sound updates #2 for 4.7-rc1

This is the second update round for 4.7-rc1.  Most of changes are
 about the pending ASoC updates and fixes, including a few new
 drivers.  Below are some highlights:
 
 ASoC:
 - New drivers for MAX98371 and TAS5720
 - SPI support for TLV320AIC32x4, along with the module split
 - TDM support for STI Uniperf IPs
 - Remaining topology API fixes / updates
 
 HDA:
 - A couple of Dell quirks and new Realtek codec support
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2
 
 iQIcBAABCAAGBQJXSLGwAAoJEGwxgFQ9KSmkKz4P/2xpAXcwjT/g/WgeNVsZLnxd
 vs1KlMPSWXLHY7ESZB+oDYtw5pAQWta2gKnG3T0QpkEtyqcyvEAUch55SfPbkDWz
 bRwboK91NF9Cfrso+QnUG1HdpaeDsNydiAR5u2sdemQG+rh8TmWXNmFsuqPptjbm
 LP6Spf8Ia2TYAvagZOB+2UTl7Jq8jMXiYP3aGWMHm7P/kREMQkSWcQ9U8F8UK92G
 5D0qKGvChsd23ybGUL1nBM7wBvErFoKd4Xa1zMudQt8EkTjistdgm24v3PO+lKDv
 JYiEugOZctzqtQVUlQMXcIqrlsafXwJN7ttKGst9gj32bM+a7EW0TGG0KyhxXI5w
 fRgGU7AJwncs9hBzEPBfc6Jms85THN2HpusU61ZYpyFAhLnHAOL7iIZnNKY8Pyyg
 tOPY2lTwHD9ic9EiC33/IypT50n0lBOi7X+YE7lGWdm2jXNvxtFv1jUw99kx25fj
 UaFNQaDYXXDKO1POCFrHpIq+jJ71Jmk7mXktI75wfuLyX3PSPyFg8OBbYVbTWkbL
 xdSqBs6LCESZ1iV9mauxwPSex44BpaMB3E0TA+7iN3+0Uwdfxe0OoLnX6dGiLSZJ
 QenFu/EDdCsA8rlrDy7AS1e5ulpYsTY1KGSZbfNzMdNsmD2XC3FdZHEF5PAC4wZQ
 EnNaik6InJgHlMF/6MXo
 =uNKQ
 -----END PGP SIGNATURE-----

Merge tag 'sound-4.7-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound

Pull more sound updates from Takashi Iwai:
 "This is the second update round for 4.7-rc1.  Most of changes are
  about the pending ASoC updates and fixes, including a few new drivers.
  Below are some highlights:

  ASoC:
   - New drivers for MAX98371 and TAS5720
   - SPI support for TLV320AIC32x4, along with the module split
   - TDM support for STI Uniperf IPs
   - Remaining topology API fixes / updates

  HDA:
   - A couple of Dell quirks and new Realtek codec support"

* tag 'sound-4.7-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (63 commits)
  ALSA: hda - Fix headset mic detection problem for one Dell machine
  spi: spi-ep93xx: Fix the PTR_ERR() argument
  ALSA: hda/realtek - Add support for ALC295/ALC3254
  ASoC: kirkwood: fix build failure
  ALSA: hda - Fix headphone noise on Dell XPS 13 9360
  ASoC: ak4642: Enable cache usage to fix crashes on resume
  ASoC: twl6040: Disconnect AUX output pads on digital mute
  ASoC: tlv320aic32x4: Properly implement the positive and negative pins into the mixers
  rcar: src: skip disabled-SRC nodes
  ASoC: max98371 Remove duplicate entry in max98371_reg
  ASoC: twl6040: Select LPPLL during standby
  ASoC: rsnd: don't use prohibited number to PDMACHCRn.SRS
  ASoC: simple-card: Add pm callbacks to platform driver
  ASoC: pxa: Fix module autoload for platform drivers
  ASoC: topology: Fix memory leak in widget creation
  ASoC: Add max98371 codec driver
  ASoC: rsnd: count .probe/.remove for rsnd_mod_call()
  ASoC: topology: Check size mismatch of ABI objects before parsing
  ASoC: topology: Check failure to create a widget
  ASoC: add support for TAS5720 digital amplifier
  ...
This commit is contained in:
Linus Torvalds 2016-05-28 12:23:12 -07:00
commit 0723ab4a97
56 changed files with 2799 additions and 330 deletions

View file

@ -0,0 +1,17 @@
max98371 codec
This device supports I2C mode only.
Required properties:
- compatible : "maxim,max98371"
- reg : The chip select number on the I2C bus
Example:
&i2c {
max98371: max98371@0x31 {
compatible = "maxim,max98371";
reg = <0x31>;
};
};

View file

@ -1,15 +1,16 @@
MT8173 with RT5650 RT5676 CODECS
MT8173 with RT5650 RT5676 CODECS and HDMI via I2S
Required properties:
- compatible : "mediatek,mt8173-rt5650-rt5676"
- mediatek,audio-codec: the phandles of rt5650 and rt5676 codecs
and of the hdmi encoder node
- mediatek,platform: the phandle of MT8173 ASoC platform
Example:
sound {
compatible = "mediatek,mt8173-rt5650-rt5676";
mediatek,audio-codec = <&rt5650 &rt5676>;
mediatek,audio-codec = <&rt5650 &rt5676 &hdmi0>;
mediatek,platform = <&afe>;
};

View file

@ -5,11 +5,21 @@ Required properties:
- mediatek,audio-codec: the phandles of rt5650 codecs
- mediatek,platform: the phandle of MT8173 ASoC platform
Optional subnodes:
- codec-capture : the subnode of rt5650 codec capture
Required codec-capture subnode properties:
- sound-dai: audio codec dai name on capture path
<&rt5650 0> : Default setting. Connect rt5650 I2S1 for capture. (dai_name = rt5645-aif1)
<&rt5650 1> : Connect rt5650 I2S2 for capture. (dai_name = rt5645-aif2)
Example:
sound {
compatible = "mediatek,mt8173-rt5650";
mediatek,audio-codec = <&rt5650>;
mediatek,platform = <&afe>;
codec-capture {
sound-dai = <&rt5650 1>;
};
};

View file

@ -37,17 +37,18 @@ Required properties:
- dai-name: DAI name that describes the IP.
- IP mode: IP working mode depending on associated codec.
"HDMI" connected to HDMI codec and support IEC HDMI formats (player only).
"SPDIF" connected to SPDIF codec and support SPDIF formats (player only).
"PCM" PCM standard mode for I2S or TDM bus.
"TDM" TDM mode for TDM bus.
Required properties ("st,sti-uni-player" compatibility only):
- clocks: CPU_DAI IP clock source, listed in the same order than the
CPU_DAI properties.
- uniperiph-id: internal SOC IP instance ID.
- IP mode: IP working mode depending on associated codec.
"HDMI" connected to HDMI codec IP and IEC HDMI formats.
"SPDIF"connected to SPDIF codec and support SPDIF formats.
"PCM" PCM standard mode for I2S or TDM bus.
Optional properties:
- pinctrl-0: defined for CPU_DAI@1 and CPU_DAI@4 to describe I2S PIOs for
external codecs connection.
@ -56,6 +57,22 @@ Optional properties:
Example:
sti_uni_player1: sti-uni-player@1 {
compatible = "st,sti-uni-player";
status = "okay";
#sound-dai-cells = <0>;
st,syscfg = <&syscfg_core>;
clocks = <&clk_s_d0_flexgen CLK_PCM_1>;
reg = <0x8D81000 0x158>;
interrupts = <GIC_SPI 85 IRQ_TYPE_NONE>;
dmas = <&fdma0 3 0 1>;
st,dai-name = "Uni Player #1 (I2S)";
dma-names = "tx";
st,uniperiph-id = <1>;
st,version = <5>;
st,mode = "TDM";
};
sti_uni_player2: sti-uni-player@2 {
compatible = "st,sti-uni-player";
status = "okay";
@ -65,7 +82,7 @@ Example:
reg = <0x8D82000 0x158>;
interrupts = <GIC_SPI 86 IRQ_TYPE_NONE>;
dmas = <&fdma0 4 0 1>;
dai-name = "Uni Player #1 (DAC)";
dai-name = "Uni Player #2 (DAC)";
dma-names = "tx";
uniperiph-id = <2>;
version = <5>;
@ -82,7 +99,7 @@ Example:
interrupts = <GIC_SPI 89 IRQ_TYPE_NONE>;
dmas = <&fdma0 7 0 1>;
dma-names = "tx";
dai-name = "Uni Player #1 (PIO)";
dai-name = "Uni Player #3 (SPDIF)";
uniperiph-id = <3>;
version = <5>;
mode = "SPDIF";
@ -99,6 +116,7 @@ Example:
dma-names = "rx";
dai-name = "Uni Reader #1 (HDMI RX)";
version = <3>;
st,mode = "PCM";
};
2) sti-sas-codec: internal audio codec IPs driver
@ -152,4 +170,20 @@ Example of audio card declaration:
sound-dai = <&sti_sasg_codec 0>;
};
};
simple-audio-card,dai-link@2 {
/* TDM playback */
format = "left_j";
frame-inversion = <1>;
cpu {
sound-dai = <&sti_uni_player1>;
dai-tdm-slot-num = <16>;
dai-tdm-slot-width = <16>;
dai-tdm-slot-tx-mask =
<1 1 1 1 0 0 0 0 0 0 1 1 0 0 1 1>;
};
codec {
sound-dai = <&sti_sasg_codec 3>;
};
};
};

View file

@ -1,4 +1,4 @@
Texas Instruments TAS5711/TAS5717/TAS5719 stereo power amplifiers
Texas Instruments TAS5711/TAS5717/TAS5719/TAS5721 stereo power amplifiers
The codec is controlled through an I2C interface. It also has two other
signals that can be wired up to GPIOs: reset (strongly recommended), and
@ -6,7 +6,11 @@ powerdown (optional).
Required properties:
- compatible: "ti,tas5711", "ti,tas5717", or "ti,tas5719"
- compatible: should be one of the following:
- "ti,tas5711",
- "ti,tas5717",
- "ti,tas5719",
- "ti,tas5721"
- reg: The I2C address of the device
- #sound-dai-cells: must be equal to 0
@ -25,6 +29,8 @@ Optional properties:
- PVDD_B-supply: regulator phandle for the PVDD_B supply (5711)
- PVDD_C-supply: regulator phandle for the PVDD_C supply (5711)
- PVDD_D-supply: regulator phandle for the PVDD_D supply (5711)
- DRVDD-supply: regulator phandle for the DRVDD supply (5721)
- PVDD-supply: regulator phandle for the PVDD supply (5721)
Example:

View file

@ -0,0 +1,25 @@
Texas Instruments TAS5720 Mono Audio amplifier
The TAS5720 serial control bus communicates through the I2C protocol only. The
serial bus is also used for periodic codec fault checking/reporting during
audio playback. For more product information please see the links below:
http://www.ti.com/product/TAS5720L
http://www.ti.com/product/TAS5720M
Required properties:
- compatible : "ti,tas5720"
- reg : I2C slave address
- dvdd-supply : phandle to a 3.3-V supply for the digital circuitry
- pvdd-supply : phandle to a supply used for the Class-D amp and the analog
Example:
tas5720: tas5720@6c {
status = "okay";
compatible = "ti,tas5720";
reg = <0x6c>;
dvdd-supply = <&vdd_3v3_reg>;
pvdd-supply = <&amp_supply_reg>;
};

View file

@ -567,7 +567,7 @@ static void ep93xx_spi_dma_transfer(struct ep93xx_spi *espi)
txd = ep93xx_spi_dma_prepare(espi, DMA_MEM_TO_DEV);
if (IS_ERR(txd)) {
ep93xx_spi_dma_finish(espi, DMA_DEV_TO_MEM);
dev_err(&espi->pdev->dev, "DMA TX failed: %ld\n", PTR_ERR(rxd));
dev_err(&espi->pdev->dev, "DMA TX failed: %ld\n", PTR_ERR(txd));
msg->status = PTR_ERR(txd);
return;
}

View file

@ -134,6 +134,7 @@
#define TWL6040_HFDACENA (1 << 0)
#define TWL6040_HFPGAENA (1 << 1)
#define TWL6040_HFDRVENA (1 << 4)
#define TWL6040_HFSWENA (1 << 6)
/* VIBCTLL/R (0x18/0x1A) fields */

View file

@ -116,6 +116,14 @@
#define SND_SOC_TPLG_STREAM_PLAYBACK 0
#define SND_SOC_TPLG_STREAM_CAPTURE 1
/* vendor tuple types */
#define SND_SOC_TPLG_TUPLE_TYPE_UUID 0
#define SND_SOC_TPLG_TUPLE_TYPE_STRING 1
#define SND_SOC_TPLG_TUPLE_TYPE_BOOL 2
#define SND_SOC_TPLG_TUPLE_TYPE_BYTE 3
#define SND_SOC_TPLG_TUPLE_TYPE_WORD 4
#define SND_SOC_TPLG_TUPLE_TYPE_SHORT 5
/*
* Block Header.
* This header precedes all object and object arrays below.
@ -132,6 +140,35 @@ struct snd_soc_tplg_hdr {
__le32 count; /* number of elements in block */
} __attribute__((packed));
/* vendor tuple for uuid */
struct snd_soc_tplg_vendor_uuid_elem {
__le32 token;
char uuid[16];
} __attribute__((packed));
/* vendor tuple for a bool/byte/short/word value */
struct snd_soc_tplg_vendor_value_elem {
__le32 token;
__le32 value;
} __attribute__((packed));
/* vendor tuple for string */
struct snd_soc_tplg_vendor_string_elem {
__le32 token;
char string[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
} __attribute__((packed));
struct snd_soc_tplg_vendor_array {
__le32 size; /* size in bytes of the array, including all elements */
__le32 type; /* SND_SOC_TPLG_TUPLE_TYPE_ */
__le32 num_elems; /* number of elements in array */
union {
struct snd_soc_tplg_vendor_uuid_elem uuid[0];
struct snd_soc_tplg_vendor_value_elem value[0];
struct snd_soc_tplg_vendor_string_elem string[0];
};
} __attribute__((packed));
/*
* Private data.
* All topology objects may have private data that can be used by the driver or
@ -139,7 +176,10 @@ struct snd_soc_tplg_hdr {
*/
struct snd_soc_tplg_private {
__le32 size; /* in bytes of private data */
char data[0];
union {
char data[0];
struct snd_soc_tplg_vendor_array array[0];
};
} __attribute__((packed));
/*
@ -383,7 +423,7 @@ struct snd_soc_tplg_pcm {
__le32 size; /* in bytes of this structure */
char pcm_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
char dai_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
__le32 pcm_id; /* unique ID - used to match */
__le32 pcm_id; /* unique ID - used to match with DAI link */
__le32 dai_id; /* unique ID - used to match */
__le32 playback; /* supports playback mode */
__le32 capture; /* supports capture mode */

View file

@ -335,6 +335,7 @@ static void alc_fill_eapd_coef(struct hda_codec *codec)
case 0x10ec0283:
case 0x10ec0286:
case 0x10ec0288:
case 0x10ec0295:
case 0x10ec0298:
alc_update_coef_idx(codec, 0x10, 1<<9, 0);
break;
@ -907,6 +908,7 @@ static struct alc_codec_rename_pci_table rename_pci_tbl[] = {
{ 0x10ec0298, 0x1028, 0, "ALC3266" },
{ 0x10ec0256, 0x1028, 0, "ALC3246" },
{ 0x10ec0225, 0x1028, 0, "ALC3253" },
{ 0x10ec0295, 0x1028, 0, "ALC3254" },
{ 0x10ec0670, 0x1025, 0, "ALC669X" },
{ 0x10ec0676, 0x1025, 0, "ALC679X" },
{ 0x10ec0282, 0x1043, 0, "ALC3229" },
@ -3697,6 +3699,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec)
alc_process_coef_fw(codec, coef0668);
break;
case 0x10ec0225:
case 0x10ec0295:
alc_process_coef_fw(codec, coef0225);
break;
}
@ -3797,6 +3800,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin,
snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50);
break;
case 0x10ec0225:
case 0x10ec0295:
alc_update_coef_idx(codec, 0x45, 0x3f<<10, 0x31<<10);
snd_hda_set_pin_ctl_cache(codec, hp_pin, 0);
alc_process_coef_fw(codec, coef0225);
@ -3854,6 +3858,7 @@ static void alc_headset_mode_default(struct hda_codec *codec)
switch (codec->core.vendor_id) {
case 0x10ec0225:
case 0x10ec0295:
alc_process_coef_fw(codec, coef0225);
break;
case 0x10ec0255:
@ -3957,6 +3962,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec)
alc_process_coef_fw(codec, coef0688);
break;
case 0x10ec0225:
case 0x10ec0295:
alc_process_coef_fw(codec, coef0225);
break;
}
@ -4038,6 +4044,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec)
alc_process_coef_fw(codec, coef0688);
break;
case 0x10ec0225:
case 0x10ec0295:
alc_process_coef_fw(codec, coef0225);
break;
}
@ -4121,6 +4128,7 @@ static void alc_determine_headset_type(struct hda_codec *codec)
is_ctia = (val & 0x1c02) == 0x1c02;
break;
case 0x10ec0225:
case 0x10ec0295:
alc_process_coef_fw(codec, coef0225);
msleep(800);
val = alc_read_coef_idx(codec, 0x46);
@ -5466,8 +5474,9 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1028, 0x06de, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK),
SND_PCI_QUIRK(0x1028, 0x06df, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK),
SND_PCI_QUIRK(0x1028, 0x06e0, "Dell", ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK),
SND_PCI_QUIRK(0x1028, 0x0704, "Dell XPS 13", ALC256_FIXUP_DELL_XPS_13_HEADPHONE_NOISE),
SND_PCI_QUIRK(0x1028, 0x0704, "Dell XPS 13 9350", ALC256_FIXUP_DELL_XPS_13_HEADPHONE_NOISE),
SND_PCI_QUIRK(0x1028, 0x0725, "Dell Inspiron 3162", ALC255_FIXUP_DELL_SPK_NOISE),
SND_PCI_QUIRK(0x1028, 0x075b, "Dell XPS 13 9360", ALC256_FIXUP_DELL_XPS_13_HEADPHONE_NOISE),
SND_PCI_QUIRK(0x1028, 0x164a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2),
@ -5710,6 +5719,9 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL2_MIC_NO_PRESENCE,
{0x14, 0x90170110},
{0x21, 0x02211020}),
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
{0x14, 0x90170130},
{0x21, 0x02211040}),
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
{0x12, 0x90a60140},
{0x14, 0x90170110},
@ -6033,6 +6045,7 @@ static int patch_alc269(struct hda_codec *codec)
alc_update_coef_idx(codec, 0x36, 1 << 13, 1 << 5); /* Switch pcbeep path to Line in path*/
break;
case 0x10ec0225:
case 0x10ec0295:
spec->codec_variant = ALC269_TYPE_ALC225;
break;
case 0x10ec0234:
@ -6979,6 +6992,7 @@ static const struct hda_device_id snd_hda_id_realtek[] = {
HDA_CODEC_ENTRY(0x10ec0292, "ALC292", patch_alc269),
HDA_CODEC_ENTRY(0x10ec0293, "ALC293", patch_alc269),
HDA_CODEC_ENTRY(0x10ec0294, "ALC294", patch_alc269),
HDA_CODEC_ENTRY(0x10ec0295, "ALC295", patch_alc269),
HDA_CODEC_ENTRY(0x10ec0298, "ALC298", patch_alc269),
HDA_CODEC_REV_ENTRY(0x10ec0861, 0x100340, "ALC660", patch_alc861),
HDA_CODEC_ENTRY(0x10ec0660, "ALC660-VD", patch_alc861vd),

View file

@ -43,6 +43,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AK5386
select SND_SOC_ALC5623 if I2C
select SND_SOC_ALC5632 if I2C
select SND_SOC_BT_SCO
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS35L32 if I2C
select SND_SOC_CS42L51_I2C if I2C
@ -64,7 +65,6 @@ config SND_SOC_ALL_CODECS
select SND_SOC_DA732X if I2C
select SND_SOC_DA9055 if I2C
select SND_SOC_DMIC
select SND_SOC_BT_SCO
select SND_SOC_ES8328_SPI if SPI_MASTER
select SND_SOC_ES8328_I2C if I2C
select SND_SOC_GTM601
@ -79,6 +79,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_MAX98090 if I2C
select SND_SOC_MAX98095 if I2C
select SND_SOC_MAX98357A if GPIOLIB
select SND_SOC_MAX98371 if I2C
select SND_SOC_MAX9867 if I2C
select SND_SOC_MAX98925 if I2C
select SND_SOC_MAX98926 if I2C
@ -126,12 +127,14 @@ config SND_SOC_ALL_CODECS
select SND_SOC_TAS2552 if I2C
select SND_SOC_TAS5086 if I2C
select SND_SOC_TAS571X if I2C
select SND_SOC_TAS5720 if I2C
select SND_SOC_TFA9879 if I2C
select SND_SOC_TLV320AIC23_I2C if I2C
select SND_SOC_TLV320AIC23_SPI if SPI_MASTER
select SND_SOC_TLV320AIC26 if SPI_MASTER
select SND_SOC_TLV320AIC31XX if I2C
select SND_SOC_TLV320AIC32X4 if I2C
select SND_SOC_TLV320AIC32X4_I2C if I2C
select SND_SOC_TLV320AIC32X4_SPI if SPI_MASTER
select SND_SOC_TLV320AIC3X if I2C
select SND_SOC_TPA6130A2 if I2C
select SND_SOC_TLV320DAC33 if I2C
@ -367,6 +370,9 @@ config SND_SOC_ALC5623
config SND_SOC_ALC5632
tristate
config SND_SOC_BT_SCO
tristate
config SND_SOC_CQ0093VC
tristate
@ -473,9 +479,6 @@ config SND_SOC_DA732X
config SND_SOC_DA9055
tristate
config SND_SOC_BT_SCO
tristate
config SND_SOC_DMIC
tristate
@ -529,6 +532,9 @@ config SND_SOC_MAX98095
config SND_SOC_MAX98357A
tristate
config SND_SOC_MAX98371
tristate
config SND_SOC_MAX9867
tristate
@ -748,9 +754,16 @@ config SND_SOC_TAS5086
depends on I2C
config SND_SOC_TAS571X
tristate "Texas Instruments TAS5711/TAS5717/TAS5719 power amplifiers"
tristate "Texas Instruments TAS5711/TAS5717/TAS5719/TAS5721 power amplifiers"
depends on I2C
config SND_SOC_TAS5720
tristate "Texas Instruments TAS5720 Mono Audio amplifier"
depends on I2C
help
Enable support for Texas Instruments TAS5720L/M high-efficiency mono
Class-D audio power amplifiers.
config SND_SOC_TFA9879
tristate "NXP Semiconductors TFA9879 amplifier"
depends on I2C
@ -780,6 +793,16 @@ config SND_SOC_TLV320AIC31XX
config SND_SOC_TLV320AIC32X4
tristate
config SND_SOC_TLV320AIC32X4_I2C
tristate
depends on I2C
select SND_SOC_TLV320AIC32X4
config SND_SOC_TLV320AIC32X4_SPI
tristate
depends on SPI_MASTER
select SND_SOC_TLV320AIC32X4
config SND_SOC_TLV320AIC3X
tristate "Texas Instruments TLV320AIC3x CODECs"
depends on I2C
@ -920,7 +943,8 @@ config SND_SOC_WM8955
tristate
config SND_SOC_WM8960
tristate
tristate "Wolfson Microelectronics WM8960 CODEC"
depends on I2C
config SND_SOC_WM8961
tristate

View file

@ -32,6 +32,7 @@ snd-soc-ak4642-objs := ak4642.o
snd-soc-ak4671-objs := ak4671.o
snd-soc-ak5386-objs := ak5386.o
snd-soc-arizona-objs := arizona.o
snd-soc-bt-sco-objs := bt-sco.o
snd-soc-cq93vc-objs := cq93vc.o
snd-soc-cs35l32-objs := cs35l32.o
snd-soc-cs42l51-objs := cs42l51.o
@ -55,7 +56,6 @@ snd-soc-da7218-objs := da7218.o
snd-soc-da7219-objs := da7219.o da7219-aad.o
snd-soc-da732x-objs := da732x.o
snd-soc-da9055-objs := da9055.o
snd-soc-bt-sco-objs := bt-sco.o
snd-soc-dmic-objs := dmic.o
snd-soc-es8328-objs := es8328.o
snd-soc-es8328-i2c-objs := es8328-i2c.o
@ -74,6 +74,7 @@ snd-soc-max98088-objs := max98088.o
snd-soc-max98090-objs := max98090.o
snd-soc-max98095-objs := max98095.o
snd-soc-max98357a-objs := max98357a.o
snd-soc-max98371-objs := max98371.o
snd-soc-max9867-objs := max9867.o
snd-soc-max98925-objs := max98925.o
snd-soc-max98926-objs := max98926.o
@ -131,6 +132,7 @@ snd-soc-stac9766-objs := stac9766.o
snd-soc-sti-sas-objs := sti-sas.o
snd-soc-tas5086-objs := tas5086.o
snd-soc-tas571x-objs := tas571x.o
snd-soc-tas5720-objs := tas5720.o
snd-soc-tfa9879-objs := tfa9879.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o
@ -138,6 +140,8 @@ snd-soc-tlv320aic23-spi-objs := tlv320aic23-spi.o
snd-soc-tlv320aic26-objs := tlv320aic26.o
snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o
snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o
snd-soc-tlv320aic32x4-i2c-objs := tlv320aic32x4-i2c.o
snd-soc-tlv320aic32x4-spi-objs := tlv320aic32x4-spi.o
snd-soc-tlv320aic3x-objs := tlv320aic3x.o
snd-soc-tlv320dac33-objs := tlv320dac33.o
snd-soc-ts3a227e-objs := ts3a227e.o
@ -243,6 +247,7 @@ obj-$(CONFIG_SND_SOC_AK5386) += snd-soc-ak5386.o
obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o
obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o
obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
@ -266,7 +271,6 @@ obj-$(CONFIG_SND_SOC_DA7218) += snd-soc-da7218.o
obj-$(CONFIG_SND_SOC_DA7219) += snd-soc-da7219.o
obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o
obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o
obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o
obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o
obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
@ -339,6 +343,7 @@ obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o
obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o
obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o
obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o
obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o
obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o
@ -346,6 +351,8 @@ obj-$(CONFIG_SND_SOC_TLV320AIC23_SPI) += snd-soc-tlv320aic23-spi.o
obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
obj-$(CONFIG_SND_SOC_TLV320AIC31XX) += snd-soc-tlv320aic31xx.o
obj-$(CONFIG_SND_SOC_TLV320AIC32X4) += snd-soc-tlv320aic32x4.o
obj-$(CONFIG_SND_SOC_TLV320AIC32X4_I2C) += snd-soc-tlv320aic32x4-i2c.o
obj-$(CONFIG_SND_SOC_TLV320AIC32X4_SPI) += snd-soc-tlv320aic32x4-spi.o
obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o
obj-$(CONFIG_SND_SOC_TS3A227E) += snd-soc-ts3a227e.o

View file

@ -560,6 +560,7 @@ static const struct regmap_config ak4642_regmap = {
.max_register = FIL1_3,
.reg_defaults = ak4642_reg,
.num_reg_defaults = NUM_AK4642_REG_DEFAULTS,
.cache_type = REGCACHE_RBTREE,
};
static const struct regmap_config ak4643_regmap = {
@ -568,6 +569,7 @@ static const struct regmap_config ak4643_regmap = {
.max_register = SPK_MS,
.reg_defaults = ak4643_reg,
.num_reg_defaults = ARRAY_SIZE(ak4643_reg),
.cache_type = REGCACHE_RBTREE,
};
static const struct regmap_config ak4648_regmap = {
@ -576,6 +578,7 @@ static const struct regmap_config ak4648_regmap = {
.max_register = EQ_FBEQE,
.reg_defaults = ak4648_reg,
.num_reg_defaults = ARRAY_SIZE(ak4648_reg),
.cache_type = REGCACHE_RBTREE,
};
static const struct ak4642_drvdata ak4642_drvdata = {

441
sound/soc/codecs/max98371.c Normal file
View file

@ -0,0 +1,441 @@
/*
* max98371.c -- ALSA SoC Stereo MAX98371 driver
*
* Copyright 2015-16 Maxim Integrated Products
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "max98371.h"
static const char *const monomix_text[] = {
"Left", "Right", "LeftRightDiv2",
};
static const char *const hpf_cutoff_txt[] = {
"Disable", "DC Block", "50Hz",
"100Hz", "200Hz", "400Hz", "800Hz",
};
static SOC_ENUM_SINGLE_DECL(max98371_monomix, MAX98371_MONOMIX_CFG, 0,
monomix_text);
static SOC_ENUM_SINGLE_DECL(max98371_hpf_cutoff, MAX98371_HPF, 0,
hpf_cutoff_txt);
static const DECLARE_TLV_DB_RANGE(max98371_dht_min_gain,
0, 1, TLV_DB_SCALE_ITEM(537, 66, 0),
2, 3, TLV_DB_SCALE_ITEM(677, 82, 0),
4, 5, TLV_DB_SCALE_ITEM(852, 104, 0),
6, 7, TLV_DB_SCALE_ITEM(1072, 131, 0),
8, 9, TLV_DB_SCALE_ITEM(1350, 165, 0),
10, 11, TLV_DB_SCALE_ITEM(1699, 101, 0),
);
static const DECLARE_TLV_DB_RANGE(max98371_dht_max_gain,
0, 1, TLV_DB_SCALE_ITEM(537, 66, 0),
2, 3, TLV_DB_SCALE_ITEM(677, 82, 0),
4, 5, TLV_DB_SCALE_ITEM(852, 104, 0),
6, 7, TLV_DB_SCALE_ITEM(1072, 131, 0),
8, 9, TLV_DB_SCALE_ITEM(1350, 165, 0),
10, 11, TLV_DB_SCALE_ITEM(1699, 208, 0),
);
static const DECLARE_TLV_DB_RANGE(max98371_dht_rot_gain,
0, 1, TLV_DB_SCALE_ITEM(-50, -50, 0),
2, 6, TLV_DB_SCALE_ITEM(-100, -100, 0),
7, 8, TLV_DB_SCALE_ITEM(-800, -200, 0),
9, 11, TLV_DB_SCALE_ITEM(-1200, -300, 0),
12, 13, TLV_DB_SCALE_ITEM(-2000, -200, 0),
14, 15, TLV_DB_SCALE_ITEM(-2500, -500, 0),
);
static const struct reg_default max98371_reg[] = {
{ 0x01, 0x00 },
{ 0x02, 0x00 },
{ 0x03, 0x00 },
{ 0x04, 0x00 },
{ 0x05, 0x00 },
{ 0x06, 0x00 },
{ 0x07, 0x00 },
{ 0x08, 0x00 },
{ 0x09, 0x00 },
{ 0x0A, 0x00 },
{ 0x10, 0x06 },
{ 0x11, 0x08 },
{ 0x14, 0x80 },
{ 0x15, 0x00 },
{ 0x16, 0x00 },
{ 0x18, 0x00 },
{ 0x19, 0x00 },
{ 0x1C, 0x00 },
{ 0x1D, 0x00 },
{ 0x1E, 0x00 },
{ 0x1F, 0x00 },
{ 0x20, 0x00 },
{ 0x21, 0x00 },
{ 0x22, 0x00 },
{ 0x23, 0x00 },
{ 0x24, 0x00 },
{ 0x25, 0x00 },
{ 0x26, 0x00 },
{ 0x27, 0x00 },
{ 0x28, 0x00 },
{ 0x29, 0x00 },
{ 0x2A, 0x00 },
{ 0x2B, 0x00 },
{ 0x2C, 0x00 },
{ 0x2D, 0x00 },
{ 0x2E, 0x0B },
{ 0x31, 0x00 },
{ 0x32, 0x18 },
{ 0x33, 0x00 },
{ 0x34, 0x00 },
{ 0x36, 0x00 },
{ 0x37, 0x00 },
{ 0x38, 0x00 },
{ 0x39, 0x00 },
{ 0x3A, 0x00 },
{ 0x3B, 0x00 },
{ 0x3C, 0x00 },
{ 0x3D, 0x00 },
{ 0x3E, 0x00 },
{ 0x3F, 0x00 },
{ 0x40, 0x00 },
{ 0x41, 0x00 },
{ 0x42, 0x00 },
{ 0x43, 0x00 },
{ 0x4A, 0x00 },
{ 0x4B, 0x00 },
{ 0x4C, 0x00 },
{ 0x4D, 0x00 },
{ 0x4E, 0x00 },
{ 0x50, 0x00 },
{ 0x51, 0x00 },
{ 0x55, 0x00 },
{ 0x58, 0x00 },
{ 0x59, 0x00 },
{ 0x5C, 0x00 },
{ 0xFF, 0x43 },
};
static bool max98371_volatile_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case MAX98371_IRQ_CLEAR1:
case MAX98371_IRQ_CLEAR2:
case MAX98371_IRQ_CLEAR3:
case MAX98371_VERSION:
return true;
default:
return false;
}
}
static bool max98371_readable_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case MAX98371_SOFT_RESET:
return false;
default:
return true;
}
};
static const DECLARE_TLV_DB_RANGE(max98371_gain_tlv,
0, 7, TLV_DB_SCALE_ITEM(0, 50, 0),
8, 10, TLV_DB_SCALE_ITEM(400, 100, 0)
);
static const DECLARE_TLV_DB_RANGE(max98371_noload_gain_tlv,
0, 11, TLV_DB_SCALE_ITEM(950, 100, 0),
);
static const DECLARE_TLV_DB_SCALE(digital_tlv, -6300, 50, 1);
static const struct snd_kcontrol_new max98371_snd_controls[] = {
SOC_SINGLE_TLV("Speaker Volume", MAX98371_GAIN,
MAX98371_GAIN_SHIFT, (1<<MAX98371_GAIN_WIDTH)-1, 0,
max98371_gain_tlv),
SOC_SINGLE_TLV("Digital Volume", MAX98371_DIGITAL_GAIN, 0,
(1<<MAX98371_DIGITAL_GAIN_WIDTH)-1, 1, digital_tlv),
SOC_SINGLE_TLV("Speaker DHT Max Volume", MAX98371_GAIN,
0, (1<<MAX98371_DHT_MAX_WIDTH)-1, 0,
max98371_dht_max_gain),
SOC_SINGLE_TLV("Speaker DHT Min Volume", MAX98371_DHT_GAIN,
0, (1<<MAX98371_DHT_GAIN_WIDTH)-1, 0,
max98371_dht_min_gain),
SOC_SINGLE_TLV("Speaker DHT Rotation Volume", MAX98371_DHT_GAIN,
0, (1<<MAX98371_DHT_ROT_WIDTH)-1, 0,
max98371_dht_rot_gain),
SOC_SINGLE("DHT Attack Step", MAX98371_DHT, MAX98371_DHT_STEP, 3, 0),
SOC_SINGLE("DHT Attack Rate", MAX98371_DHT, 0, 7, 0),
SOC_ENUM("Monomix Select", max98371_monomix),
SOC_ENUM("HPF Cutoff", max98371_hpf_cutoff),
};
static int max98371_dai_set_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
struct max98371_priv *max98371 = snd_soc_codec_get_drvdata(codec);
unsigned int val = 0;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
break;
default:
dev_err(codec->dev, "DAI clock mode unsupported");
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
val |= 0;
break;
case SND_SOC_DAIFMT_RIGHT_J:
val |= MAX98371_DAI_RIGHT;
break;
case SND_SOC_DAIFMT_LEFT_J:
val |= MAX98371_DAI_LEFT;
break;
default:
dev_err(codec->dev, "DAI wrong mode unsupported");
return -EINVAL;
}
regmap_update_bits(max98371->regmap, MAX98371_FMT,
MAX98371_FMT_MODE_MASK, val);
return 0;
}
static int max98371_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_codec *codec = dai->codec;
struct max98371_priv *max98371 = snd_soc_codec_get_drvdata(codec);
int blr_clk_ratio, ch_size, channels = params_channels(params);
int rate = params_rate(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
regmap_update_bits(max98371->regmap, MAX98371_FMT,
MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_16);
ch_size = 8;
break;
case SNDRV_PCM_FORMAT_S16_LE:
regmap_update_bits(max98371->regmap, MAX98371_FMT,
MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_16);
ch_size = 16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
regmap_update_bits(max98371->regmap, MAX98371_FMT,
MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_32);
ch_size = 24;
break;
case SNDRV_PCM_FORMAT_S32_LE:
regmap_update_bits(max98371->regmap, MAX98371_FMT,
MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_32);
ch_size = 32;
break;
default:
return -EINVAL;
}
/* BCLK/LRCLK ratio calculation */
blr_clk_ratio = channels * ch_size;
switch (blr_clk_ratio) {
case 32:
regmap_update_bits(max98371->regmap,
MAX98371_DAI_CLK,
MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_32);
break;
case 48:
regmap_update_bits(max98371->regmap,
MAX98371_DAI_CLK,
MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_48);
break;
case 64:
regmap_update_bits(max98371->regmap,
MAX98371_DAI_CLK,
MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_64);
break;
default:
return -EINVAL;
}
switch (rate) {
case 32000:
regmap_update_bits(max98371->regmap,
MAX98371_SPK_SR,
MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_32);
break;
case 44100:
regmap_update_bits(max98371->regmap,
MAX98371_SPK_SR,
MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_44);
break;
case 48000:
regmap_update_bits(max98371->regmap,
MAX98371_SPK_SR,
MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_48);
break;
case 88200:
regmap_update_bits(max98371->regmap,
MAX98371_SPK_SR,
MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_88);
break;
case 96000:
regmap_update_bits(max98371->regmap,
MAX98371_SPK_SR,
MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_96);
break;
default:
return -EINVAL;
}
/* enabling both the RX channels*/
regmap_update_bits(max98371->regmap, MAX98371_MONOMIX_SRC,
MAX98371_MONOMIX_SRC_MASK, MONOMIX_RX_0_1);
regmap_update_bits(max98371->regmap, MAX98371_DAI_CHANNEL,
MAX98371_CHANNEL_MASK, MAX98371_CHANNEL_MASK);
return 0;
}
static const struct snd_soc_dapm_widget max98371_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DAC", NULL, MAX98371_SPK_ENABLE, 0, 0),
SND_SOC_DAPM_SUPPLY("Global Enable", MAX98371_GLOBAL_ENABLE,
0, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("SPK_OUT"),
};
static const struct snd_soc_dapm_route max98371_audio_map[] = {
{"DAC", NULL, "HiFi Playback"},
{"SPK_OUT", NULL, "DAC"},
{"SPK_OUT", NULL, "Global Enable"},
};
#define MAX98371_RATES SNDRV_PCM_RATE_8000_48000
#define MAX98371_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
static const struct snd_soc_dai_ops max98371_dai_ops = {
.set_fmt = max98371_dai_set_fmt,
.hw_params = max98371_dai_hw_params,
};
static struct snd_soc_dai_driver max98371_dai[] = {
{
.name = "max98371-aif1",
.playback = {
.stream_name = "HiFi Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = MAX98371_FORMATS,
},
.ops = &max98371_dai_ops,
}
};
static const struct snd_soc_codec_driver max98371_codec = {
.controls = max98371_snd_controls,
.num_controls = ARRAY_SIZE(max98371_snd_controls),
.dapm_routes = max98371_audio_map,
.num_dapm_routes = ARRAY_SIZE(max98371_audio_map),
.dapm_widgets = max98371_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(max98371_dapm_widgets),
};
static const struct regmap_config max98371_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = MAX98371_VERSION,
.reg_defaults = max98371_reg,
.num_reg_defaults = ARRAY_SIZE(max98371_reg),
.volatile_reg = max98371_volatile_register,
.readable_reg = max98371_readable_register,
.cache_type = REGCACHE_RBTREE,
};
static int max98371_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct max98371_priv *max98371;
int ret, reg;
max98371 = devm_kzalloc(&i2c->dev,
sizeof(*max98371), GFP_KERNEL);
if (!max98371)
return -ENOMEM;
i2c_set_clientdata(i2c, max98371);
max98371->regmap = devm_regmap_init_i2c(i2c, &max98371_regmap);
if (IS_ERR(max98371->regmap)) {
ret = PTR_ERR(max98371->regmap);
dev_err(&i2c->dev,
"Failed to allocate regmap: %d\n", ret);
return ret;
}
ret = regmap_read(max98371->regmap, MAX98371_VERSION, &reg);
if (ret < 0) {
dev_info(&i2c->dev, "device error %d\n", ret);
return ret;
}
dev_info(&i2c->dev, "device version %x\n", reg);
ret = snd_soc_register_codec(&i2c->dev, &max98371_codec,
max98371_dai, ARRAY_SIZE(max98371_dai));
if (ret < 0) {
dev_err(&i2c->dev, "Failed to register codec: %d\n", ret);
return ret;
}
return ret;
}
static int max98371_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
return 0;
}
static const struct i2c_device_id max98371_i2c_id[] = {
{ "max98371", 0 },
};
MODULE_DEVICE_TABLE(i2c, max98371_i2c_id);
static const struct of_device_id max98371_of_match[] = {
{ .compatible = "maxim,max98371", },
{ }
};
MODULE_DEVICE_TABLE(of, max98371_of_match);
static struct i2c_driver max98371_i2c_driver = {
.driver = {
.name = "max98371",
.owner = THIS_MODULE,
.pm = NULL,
.of_match_table = of_match_ptr(max98371_of_match),
},
.probe = max98371_i2c_probe,
.remove = max98371_i2c_remove,
.id_table = max98371_i2c_id,
};
module_i2c_driver(max98371_i2c_driver);
MODULE_AUTHOR("anish kumar <yesanishhere@gmail.com>");
MODULE_DESCRIPTION("ALSA SoC MAX98371 driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,67 @@
/*
* max98371.h -- MAX98371 ALSA SoC Audio driver
*
* Copyright 2011-2012 Maxim Integrated Products
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef _MAX98371_H
#define _MAX98371_H
#define MAX98371_IRQ_CLEAR1 0x01
#define MAX98371_IRQ_CLEAR2 0x02
#define MAX98371_IRQ_CLEAR3 0x03
#define MAX98371_DAI_CLK 0x10
#define MAX98371_DAI_BSEL_MASK 0xF
#define MAX98371_DAI_BSEL_32 2
#define MAX98371_DAI_BSEL_48 3
#define MAX98371_DAI_BSEL_64 4
#define MAX98371_SPK_SR 0x11
#define MAX98371_SPK_SR_MASK 0xF
#define MAX98371_SPK_SR_32 6
#define MAX98371_SPK_SR_44 7
#define MAX98371_SPK_SR_48 8
#define MAX98371_SPK_SR_88 10
#define MAX98371_SPK_SR_96 11
#define MAX98371_DAI_CHANNEL 0x15
#define MAX98371_CHANNEL_MASK 0x3
#define MAX98371_MONOMIX_SRC 0x18
#define MAX98371_MONOMIX_CFG 0x19
#define MAX98371_HPF 0x1C
#define MAX98371_MONOMIX_SRC_MASK 0xFF
#define MONOMIX_RX_0_1 ((0x1)<<(4))
#define M98371_DAI_CHANNEL_I2S 0x3
#define MAX98371_DIGITAL_GAIN 0x2D
#define MAX98371_DIGITAL_GAIN_WIDTH 0x7
#define MAX98371_GAIN 0x2E
#define MAX98371_GAIN_SHIFT 0x4
#define MAX98371_GAIN_WIDTH 0x4
#define MAX98371_DHT_MAX_WIDTH 4
#define MAX98371_FMT 0x14
#define MAX98371_CHANSZ_WIDTH 6
#define MAX98371_FMT_MASK ((0x3)<<(MAX98371_CHANSZ_WIDTH))
#define MAX98371_FMT_MODE_MASK ((0x7)<<(3))
#define MAX98371_DAI_LEFT ((0x1)<<(3))
#define MAX98371_DAI_RIGHT ((0x2)<<(3))
#define MAX98371_DAI_CHANSZ_16 ((1)<<(MAX98371_CHANSZ_WIDTH))
#define MAX98371_DAI_CHANSZ_24 ((2)<<(MAX98371_CHANSZ_WIDTH))
#define MAX98371_DAI_CHANSZ_32 ((3)<<(MAX98371_CHANSZ_WIDTH))
#define MAX98371_DHT 0x32
#define MAX98371_DHT_STEP 0x3
#define MAX98371_DHT_GAIN 0x31
#define MAX98371_DHT_GAIN_WIDTH 0x4
#define MAX98371_DHT_ROT_WIDTH 0x4
#define MAX98371_SPK_ENABLE 0x4A
#define MAX98371_GLOBAL_ENABLE 0x50
#define MAX98371_SOFT_RESET 0x51
#define MAX98371_VERSION 0xFF
struct max98371_priv {
struct regmap *regmap;
struct snd_soc_codec *codec;
};
#endif

View file

@ -276,6 +276,8 @@ static int rt298_jack_detect(struct rt298_priv *rt298, bool *hp, bool *mic)
} else {
*mic = false;
regmap_write(rt298->regmap, RT298_SET_MIC1, 0x20);
regmap_update_bits(rt298->regmap,
RT298_CBJ_CTRL1, 0x0400, 0x0000);
}
} else {
regmap_read(rt298->regmap, RT298_GET_HP_SENSE, &buf);
@ -482,6 +484,26 @@ static int rt298_adc_event(struct snd_soc_dapm_widget *w,
snd_soc_update_bits(codec,
VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0),
0x7080, 0x7000);
/* If MCLK doesn't exist, reset AD filter */
if (!(snd_soc_read(codec, RT298_VAD_CTRL) & 0x200)) {
pr_info("NO MCLK\n");
switch (nid) {
case RT298_ADC_IN1:
snd_soc_update_bits(codec,
RT298_D_FILTER_CTRL, 0x2, 0x2);
mdelay(10);
snd_soc_update_bits(codec,
RT298_D_FILTER_CTRL, 0x2, 0x0);
break;
case RT298_ADC_IN2:
snd_soc_update_bits(codec,
RT298_D_FILTER_CTRL, 0x4, 0x4);
mdelay(10);
snd_soc_update_bits(codec,
RT298_D_FILTER_CTRL, 0x4, 0x0);
break;
}
}
break;
case SND_SOC_DAPM_PRE_PMD:
snd_soc_update_bits(codec,
@ -520,30 +542,12 @@ static int rt298_mic1_event(struct snd_soc_dapm_widget *w,
return 0;
}
static int rt298_vref_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
snd_soc_update_bits(codec,
RT298_CBJ_CTRL1, 0x0400, 0x0000);
mdelay(50);
break;
default:
return 0;
}
return 0;
}
static const struct snd_soc_dapm_widget rt298_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY_S("HV", 1, RT298_POWER_CTRL1,
12, 1, NULL, 0),
SND_SOC_DAPM_SUPPLY("VREF", RT298_POWER_CTRL1,
0, 1, rt298_vref_event, SND_SOC_DAPM_PRE_PMU),
0, 1, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("BG_MBIAS", 1, RT298_POWER_CTRL2,
1, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY_S("LDO1", 1, RT298_POWER_CTRL2,
@ -934,18 +938,9 @@ static int rt298_set_bias_level(struct snd_soc_codec *codec,
}
break;
case SND_SOC_BIAS_ON:
mdelay(30);
snd_soc_update_bits(codec,
RT298_CBJ_CTRL1, 0x0400, 0x0400);
break;
case SND_SOC_BIAS_STANDBY:
snd_soc_write(codec,
RT298_SET_AUDIO_POWER, AC_PWRST_D3);
snd_soc_update_bits(codec,
RT298_CBJ_CTRL1, 0x0400, 0x0000);
break;
default:

View file

@ -137,6 +137,7 @@
#define RT298_A_BIAS_CTRL2 0x02
#define RT298_POWER_CTRL1 0x03
#define RT298_A_BIAS_CTRL3 0x04
#define RT298_D_FILTER_CTRL 0x05
#define RT298_POWER_CTRL2 0x08
#define RT298_I2S_CTRL1 0x09
#define RT298_I2S_CTRL2 0x0a
@ -148,6 +149,7 @@
#define RT298_IRQ_CTRL 0x33
#define RT298_WIND_FILTER_CTRL 0x46
#define RT298_PLL_CTRL1 0x49
#define RT298_VAD_CTRL 0x4e
#define RT298_CBJ_CTRL1 0x4f
#define RT298_CBJ_CTRL2 0x50
#define RT298_PLL_CTRL 0x63

View file

@ -1241,60 +1241,46 @@ static int rt5677_dmic_use_asrc(struct snd_soc_dapm_widget *source,
regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting);
asrc_setting = (asrc_setting & RT5677_AD_STO1_CLK_SEL_MASK) >>
RT5677_AD_STO1_CLK_SEL_SFT;
if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC &&
asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC)
return 1;
break;
case 10:
regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting);
asrc_setting = (asrc_setting & RT5677_AD_STO2_CLK_SEL_MASK) >>
RT5677_AD_STO2_CLK_SEL_SFT;
if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC &&
asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC)
return 1;
break;
case 9:
regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting);
asrc_setting = (asrc_setting & RT5677_AD_STO3_CLK_SEL_MASK) >>
RT5677_AD_STO3_CLK_SEL_SFT;
if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC &&
asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC)
return 1;
break;
case 8:
regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting);
asrc_setting = (asrc_setting & RT5677_AD_STO4_CLK_SEL_MASK) >>
RT5677_AD_STO4_CLK_SEL_SFT;
if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC &&
asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC)
return 1;
break;
case 7:
regmap_read(rt5677->regmap, RT5677_ASRC_6, &asrc_setting);
asrc_setting = (asrc_setting & RT5677_AD_MONOL_CLK_SEL_MASK) >>
RT5677_AD_MONOL_CLK_SEL_SFT;
if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC &&
asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC)
return 1;
break;
case 6:
regmap_read(rt5677->regmap, RT5677_ASRC_6, &asrc_setting);
asrc_setting = (asrc_setting & RT5677_AD_MONOR_CLK_SEL_MASK) >>
RT5677_AD_MONOR_CLK_SEL_SFT;
if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC &&
asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC)
return 1;
break;
default:
break;
return 0;
}
if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC &&
asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC)
return 1;
return 0;
}

View file

@ -4,6 +4,9 @@
* Copyright (C) 2015 Google, Inc.
* Copyright (c) 2013 Daniel Mack <zonque@gmail.com>
*
* TAS5721 support:
* Copyright (C) 2016 Petr Kulhavy, Barix AG <petr@barix.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@ -57,6 +60,10 @@ static int tas571x_register_size(struct tas571x_private *priv, unsigned int reg)
case TAS571X_CH1_VOL_REG:
case TAS571X_CH2_VOL_REG:
return priv->chip->vol_reg_size;
case TAS571X_INPUT_MUX_REG:
case TAS571X_CH4_SRC_SELECT_REG:
case TAS571X_PWM_MUX_REG:
return 4;
default:
return 1;
}
@ -167,6 +174,23 @@ static int tas571x_hw_params(struct snd_pcm_substream *substream,
TAS571X_SDI_FMT_MASK, val);
}
static int tas571x_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
u8 sysctl2;
int ret;
sysctl2 = mute ? TAS571X_SYS_CTRL_2_SDN_MASK : 0;
ret = snd_soc_update_bits(codec,
TAS571X_SYS_CTRL_2_REG,
TAS571X_SYS_CTRL_2_SDN_MASK,
sysctl2);
usleep_range(1000, 2000);
return ret;
}
static int tas571x_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
@ -214,6 +238,7 @@ static int tas571x_set_bias_level(struct snd_soc_codec *codec,
static const struct snd_soc_dai_ops tas571x_dai_ops = {
.set_fmt = tas571x_set_dai_fmt,
.hw_params = tas571x_hw_params,
.digital_mute = tas571x_mute,
};
static const char *const tas5711_supply_names[] = {
@ -241,6 +266,26 @@ static const struct snd_kcontrol_new tas5711_controls[] = {
1, 1),
};
static const struct regmap_range tas571x_readonly_regs_range[] = {
regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_DEV_ID_REG),
};
static const struct regmap_range tas571x_volatile_regs_range[] = {
regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_ERR_STATUS_REG),
regmap_reg_range(TAS571X_OSC_TRIM_REG, TAS571X_OSC_TRIM_REG),
};
static const struct regmap_access_table tas571x_write_regs = {
.no_ranges = tas571x_readonly_regs_range,
.n_no_ranges = ARRAY_SIZE(tas571x_readonly_regs_range),
};
static const struct regmap_access_table tas571x_volatile_regs = {
.yes_ranges = tas571x_volatile_regs_range,
.n_yes_ranges = ARRAY_SIZE(tas571x_volatile_regs_range),
};
static const struct reg_default tas5711_reg_defaults[] = {
{ 0x04, 0x05 },
{ 0x05, 0x40 },
@ -260,6 +305,8 @@ static const struct regmap_config tas5711_regmap_config = {
.reg_defaults = tas5711_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tas5711_reg_defaults),
.cache_type = REGCACHE_RBTREE,
.wr_table = &tas571x_write_regs,
.volatile_table = &tas571x_volatile_regs,
};
static const struct tas571x_chip tas5711_chip = {
@ -314,6 +361,8 @@ static const struct regmap_config tas5717_regmap_config = {
.reg_defaults = tas5717_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tas5717_reg_defaults),
.cache_type = REGCACHE_RBTREE,
.wr_table = &tas571x_write_regs,
.volatile_table = &tas571x_volatile_regs,
};
/* This entry is reused for tas5719 as the software interface is identical. */
@ -326,6 +375,77 @@ static const struct tas571x_chip tas5717_chip = {
.vol_reg_size = 2,
};
static const char *const tas5721_supply_names[] = {
"AVDD",
"DVDD",
"DRVDD",
"PVDD",
};
static const struct snd_kcontrol_new tas5721_controls[] = {
SOC_SINGLE_TLV("Master Volume",
TAS571X_MVOL_REG,
0, 0xff, 1, tas5711_volume_tlv),
SOC_DOUBLE_R_TLV("Speaker Volume",
TAS571X_CH1_VOL_REG,
TAS571X_CH2_VOL_REG,
0, 0xff, 1, tas5711_volume_tlv),
SOC_DOUBLE("Speaker Switch",
TAS571X_SOFT_MUTE_REG,
TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT,
1, 1),
};
static const struct reg_default tas5721_reg_defaults[] = {
{TAS571X_CLK_CTRL_REG, 0x6c},
{TAS571X_DEV_ID_REG, 0x00},
{TAS571X_ERR_STATUS_REG, 0x00},
{TAS571X_SYS_CTRL_1_REG, 0xa0},
{TAS571X_SDI_REG, 0x05},
{TAS571X_SYS_CTRL_2_REG, 0x40},
{TAS571X_SOFT_MUTE_REG, 0x00},
{TAS571X_MVOL_REG, 0xff},
{TAS571X_CH1_VOL_REG, 0x30},
{TAS571X_CH2_VOL_REG, 0x30},
{TAS571X_CH3_VOL_REG, 0x30},
{TAS571X_VOL_CFG_REG, 0x91},
{TAS571X_MODULATION_LIMIT_REG, 0x02},
{TAS571X_IC_DELAY_CH1_REG, 0xac},
{TAS571X_IC_DELAY_CH2_REG, 0x54},
{TAS571X_IC_DELAY_CH3_REG, 0xac},
{TAS571X_IC_DELAY_CH4_REG, 0x54},
{TAS571X_PWM_CH_SDN_GROUP_REG, 0x30},
{TAS571X_START_STOP_PERIOD_REG, 0x0f},
{TAS571X_OSC_TRIM_REG, 0x82},
{TAS571X_BKND_ERR_REG, 0x02},
{TAS571X_INPUT_MUX_REG, 0x17772},
{TAS571X_CH4_SRC_SELECT_REG, 0x4303},
{TAS571X_PWM_MUX_REG, 0x1021345},
};
static const struct regmap_config tas5721_regmap_config = {
.reg_bits = 8,
.val_bits = 32,
.max_register = 0xff,
.reg_read = tas571x_reg_read,
.reg_write = tas571x_reg_write,
.reg_defaults = tas5721_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tas5721_reg_defaults),
.cache_type = REGCACHE_RBTREE,
.wr_table = &tas571x_write_regs,
.volatile_table = &tas571x_volatile_regs,
};
static const struct tas571x_chip tas5721_chip = {
.supply_names = tas5721_supply_names,
.num_supply_names = ARRAY_SIZE(tas5721_supply_names),
.controls = tas5711_controls,
.num_controls = ARRAY_SIZE(tas5711_controls),
.regmap_config = &tas5721_regmap_config,
.vol_reg_size = 1,
};
static const struct snd_soc_dapm_widget tas571x_dapm_widgets[] = {
SND_SOC_DAPM_DAC("DACL", NULL, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_DAC("DACR", NULL, SND_SOC_NOPM, 0, 0),
@ -386,11 +506,10 @@ static int tas571x_i2c_probe(struct i2c_client *client,
i2c_set_clientdata(client, priv);
of_id = of_match_device(tas571x_of_match, dev);
if (!of_id) {
dev_err(dev, "Unknown device type\n");
return -EINVAL;
}
priv->chip = of_id->data;
if (of_id)
priv->chip = of_id->data;
else
priv->chip = (void *) id->driver_data;
priv->mclk = devm_clk_get(dev, "mclk");
if (IS_ERR(priv->mclk) && PTR_ERR(priv->mclk) != -ENOENT) {
@ -445,10 +564,6 @@ static int tas571x_i2c_probe(struct i2c_client *client,
if (ret)
return ret;
ret = regmap_update_bits(priv->regmap, TAS571X_SYS_CTRL_2_REG,
TAS571X_SYS_CTRL_2_SDN_MASK, 0);
if (ret)
return ret;
memcpy(&priv->codec_driver, &tas571x_codec, sizeof(priv->codec_driver));
priv->codec_driver.controls = priv->chip->controls;
@ -486,14 +601,16 @@ static const struct of_device_id tas571x_of_match[] = {
{ .compatible = "ti,tas5711", .data = &tas5711_chip, },
{ .compatible = "ti,tas5717", .data = &tas5717_chip, },
{ .compatible = "ti,tas5719", .data = &tas5717_chip, },
{ .compatible = "ti,tas5721", .data = &tas5721_chip, },
{ }
};
MODULE_DEVICE_TABLE(of, tas571x_of_match);
static const struct i2c_device_id tas571x_i2c_id[] = {
{ "tas5711", 0 },
{ "tas5717", 0 },
{ "tas5719", 0 },
{ "tas5711", (kernel_ulong_t) &tas5711_chip },
{ "tas5717", (kernel_ulong_t) &tas5717_chip },
{ "tas5719", (kernel_ulong_t) &tas5717_chip },
{ "tas5721", (kernel_ulong_t) &tas5721_chip },
{ }
};
MODULE_DEVICE_TABLE(i2c, tas571x_i2c_id);

View file

@ -13,6 +13,10 @@
#define _TAS571X_H
/* device registers */
#define TAS571X_CLK_CTRL_REG 0x00
#define TAS571X_DEV_ID_REG 0x01
#define TAS571X_ERR_STATUS_REG 0x02
#define TAS571X_SYS_CTRL_1_REG 0x03
#define TAS571X_SDI_REG 0x04
#define TAS571X_SDI_FMT_MASK 0x0f
@ -27,7 +31,25 @@
#define TAS571X_MVOL_REG 0x07
#define TAS571X_CH1_VOL_REG 0x08
#define TAS571X_CH2_VOL_REG 0x09
#define TAS571X_CH3_VOL_REG 0x0a
#define TAS571X_VOL_CFG_REG 0x0e
#define TAS571X_MODULATION_LIMIT_REG 0x10
#define TAS571X_IC_DELAY_CH1_REG 0x11
#define TAS571X_IC_DELAY_CH2_REG 0x12
#define TAS571X_IC_DELAY_CH3_REG 0x13
#define TAS571X_IC_DELAY_CH4_REG 0x14
#define TAS571X_PWM_CH_SDN_GROUP_REG 0x19 /* N/A on TAS5717, TAS5719 */
#define TAS571X_PWM_CH1_SDN_MASK (1<<0)
#define TAS571X_PWM_CH2_SDN_SHIFT (1<<1)
#define TAS571X_PWM_CH3_SDN_SHIFT (1<<2)
#define TAS571X_PWM_CH4_SDN_SHIFT (1<<3)
#define TAS571X_START_STOP_PERIOD_REG 0x1a
#define TAS571X_OSC_TRIM_REG 0x1b
#define TAS571X_BKND_ERR_REG 0x1c
#define TAS571X_INPUT_MUX_REG 0x20
#define TAS571X_CH4_SRC_SELECT_REG 0x21
#define TAS571X_PWM_MUX_REG 0x25
#endif /* _TAS571X_H */

620
sound/soc/codecs/tas5720.c Normal file
View file

@ -0,0 +1,620 @@
/*
* tas5720.c - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier
*
* Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com
*
* Author: Andreas Dannenberg <dannenberg@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "tas5720.h"
/* Define how often to check (and clear) the fault status register (in ms) */
#define TAS5720_FAULT_CHECK_INTERVAL 200
static const char * const tas5720_supply_names[] = {
"dvdd", /* Digital power supply. Connect to 3.3-V supply. */
"pvdd", /* Class-D amp and analog power supply (connected). */
};
#define TAS5720_NUM_SUPPLIES ARRAY_SIZE(tas5720_supply_names)
struct tas5720_data {
struct snd_soc_codec *codec;
struct regmap *regmap;
struct i2c_client *tas5720_client;
struct regulator_bulk_data supplies[TAS5720_NUM_SUPPLIES];
struct delayed_work fault_check_work;
unsigned int last_fault;
};
static int tas5720_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_codec *codec = dai->codec;
unsigned int rate = params_rate(params);
bool ssz_ds;
int ret;
switch (rate) {
case 44100:
case 48000:
ssz_ds = false;
break;
case 88200:
case 96000:
ssz_ds = true;
break;
default:
dev_err(codec->dev, "unsupported sample rate: %u\n", rate);
return -EINVAL;
}
ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG,
TAS5720_SSZ_DS, ssz_ds);
if (ret < 0) {
dev_err(codec->dev, "error setting sample rate: %d\n", ret);
return ret;
}
return 0;
}
static int tas5720_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_codec *codec = dai->codec;
u8 serial_format;
int ret;
if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) {
dev_vdbg(codec->dev, "DAI Format master is not found\n");
return -EINVAL;
}
switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
SND_SOC_DAIFMT_INV_MASK)) {
case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF):
/* 1st data bit occur one BCLK cycle after the frame sync */
serial_format = TAS5720_SAIF_I2S;
break;
case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF):
/*
* Note that although the TAS5720 does not have a dedicated DSP
* mode it doesn't care about the LRCLK duty cycle during TDM
* operation. Therefore we can use the device's I2S mode with
* its delaying of the 1st data bit to receive DSP_A formatted
* data. See device datasheet for additional details.
*/
serial_format = TAS5720_SAIF_I2S;
break;
case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF):
/*
* Similar to DSP_A, we can use the fact that the TAS5720 does
* not care about the LRCLK duty cycle during TDM to receive
* DSP_B formatted data in LEFTJ mode (no delaying of the 1st
* data bit).
*/
serial_format = TAS5720_SAIF_LEFTJ;
break;
case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF):
/* No delay after the frame sync */
serial_format = TAS5720_SAIF_LEFTJ;
break;
default:
dev_vdbg(codec->dev, "DAI Format is not found\n");
return -EINVAL;
}
ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG,
TAS5720_SAIF_FORMAT_MASK,
serial_format);
if (ret < 0) {
dev_err(codec->dev, "error setting SAIF format: %d\n", ret);
return ret;
}
return 0;
}
static int tas5720_set_dai_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width)
{
struct snd_soc_codec *codec = dai->codec;
unsigned int first_slot;
int ret;
if (!tx_mask) {
dev_err(codec->dev, "tx masks must not be 0\n");
return -EINVAL;
}
/*
* Determine the first slot that is being requested. We will only
* use the first slot that is found since the TAS5720 is a mono
* amplifier.
*/
first_slot = __ffs(tx_mask);
if (first_slot > 7) {
dev_err(codec->dev, "slot selection out of bounds (%u)\n",
first_slot);
return -EINVAL;
}
/* Enable manual TDM slot selection (instead of I2C ID based) */
ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG,
TAS5720_TDM_CFG_SRC, TAS5720_TDM_CFG_SRC);
if (ret < 0)
goto error_snd_soc_update_bits;
/* Configure the TDM slot to process audio from */
ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG,
TAS5720_TDM_SLOT_SEL_MASK, first_slot);
if (ret < 0)
goto error_snd_soc_update_bits;
return 0;
error_snd_soc_update_bits:
dev_err(codec->dev, "error configuring TDM mode: %d\n", ret);
return ret;
}
static int tas5720_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
int ret;
ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG,
TAS5720_MUTE, mute ? TAS5720_MUTE : 0);
if (ret < 0) {
dev_err(codec->dev, "error (un-)muting device: %d\n", ret);
return ret;
}
return 0;
}
static void tas5720_fault_check_work(struct work_struct *work)
{
struct tas5720_data *tas5720 = container_of(work, struct tas5720_data,
fault_check_work.work);
struct device *dev = tas5720->codec->dev;
unsigned int curr_fault;
int ret;
ret = regmap_read(tas5720->regmap, TAS5720_FAULT_REG, &curr_fault);
if (ret < 0) {
dev_err(dev, "failed to read FAULT register: %d\n", ret);
goto out;
}
/* Check/handle all errors except SAIF clock errors */
curr_fault &= TAS5720_OCE | TAS5720_DCE | TAS5720_OTE;
/*
* Only flag errors once for a given occurrence. This is needed as
* the TAS5720 will take time clearing the fault condition internally
* during which we don't want to bombard the system with the same
* error message over and over.
*/
if ((curr_fault & TAS5720_OCE) && !(tas5720->last_fault & TAS5720_OCE))
dev_crit(dev, "experienced an over current hardware fault\n");
if ((curr_fault & TAS5720_DCE) && !(tas5720->last_fault & TAS5720_DCE))
dev_crit(dev, "experienced a DC detection fault\n");
if ((curr_fault & TAS5720_OTE) && !(tas5720->last_fault & TAS5720_OTE))
dev_crit(dev, "experienced an over temperature fault\n");
/* Store current fault value so we can detect any changes next time */
tas5720->last_fault = curr_fault;
if (!curr_fault)
goto out;
/*
* Periodically toggle SDZ (shutdown bit) H->L->H to clear any latching
* faults as long as a fault condition persists. Always going through
* the full sequence no matter the first return value to minimizes
* chances for the device to end up in shutdown mode.
*/
ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG,
TAS5720_SDZ, 0);
if (ret < 0)
dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret);
ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG,
TAS5720_SDZ, TAS5720_SDZ);
if (ret < 0)
dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret);
out:
/* Schedule the next fault check at the specified interval */
schedule_delayed_work(&tas5720->fault_check_work,
msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL));
}
static int tas5720_codec_probe(struct snd_soc_codec *codec)
{
struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec);
unsigned int device_id;
int ret;
tas5720->codec = codec;
ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies),
tas5720->supplies);
if (ret != 0) {
dev_err(codec->dev, "failed to enable supplies: %d\n", ret);
return ret;
}
ret = regmap_read(tas5720->regmap, TAS5720_DEVICE_ID_REG, &device_id);
if (ret < 0) {
dev_err(codec->dev, "failed to read device ID register: %d\n",
ret);
goto probe_fail;
}
if (device_id != TAS5720_DEVICE_ID) {
dev_err(codec->dev, "wrong device ID. expected: %u read: %u\n",
TAS5720_DEVICE_ID, device_id);
ret = -ENODEV;
goto probe_fail;
}
/* Set device to mute */
ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG,
TAS5720_MUTE, TAS5720_MUTE);
if (ret < 0)
goto error_snd_soc_update_bits;
/*
* Enter shutdown mode - our default when not playing audio - to
* minimize current consumption. On the TAS5720 there is no real down
* side doing so as all device registers are preserved and the wakeup
* of the codec is rather quick which we do using a dapm widget.
*/
ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG,
TAS5720_SDZ, 0);
if (ret < 0)
goto error_snd_soc_update_bits;
INIT_DELAYED_WORK(&tas5720->fault_check_work, tas5720_fault_check_work);
return 0;
error_snd_soc_update_bits:
dev_err(codec->dev, "error configuring device registers: %d\n", ret);
probe_fail:
regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies),
tas5720->supplies);
return ret;
}
static int tas5720_codec_remove(struct snd_soc_codec *codec)
{
struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec);
int ret;
cancel_delayed_work_sync(&tas5720->fault_check_work);
ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies),
tas5720->supplies);
if (ret < 0)
dev_err(codec->dev, "failed to disable supplies: %d\n", ret);
return ret;
};
static int tas5720_dac_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec);
int ret;
if (event & SND_SOC_DAPM_POST_PMU) {
/* Take TAS5720 out of shutdown mode */
ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG,
TAS5720_SDZ, TAS5720_SDZ);
if (ret < 0) {
dev_err(codec->dev, "error waking codec: %d\n", ret);
return ret;
}
/*
* Observe codec shutdown-to-active time. The datasheet only
* lists a nominal value however just use-it as-is without
* additional padding to minimize the delay introduced in
* starting to play audio (actually there is other setup done
* by the ASoC framework that will provide additional delays,
* so we should always be safe).
*/
msleep(25);
/* Turn on TAS5720 periodic fault checking/handling */
tas5720->last_fault = 0;
schedule_delayed_work(&tas5720->fault_check_work,
msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL));
} else if (event & SND_SOC_DAPM_PRE_PMD) {
/* Disable TAS5720 periodic fault checking/handling */
cancel_delayed_work_sync(&tas5720->fault_check_work);
/* Place TAS5720 in shutdown mode to minimize current draw */
ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG,
TAS5720_SDZ, 0);
if (ret < 0) {
dev_err(codec->dev, "error shutting down codec: %d\n",
ret);
return ret;
}
}
return 0;
}
#ifdef CONFIG_PM
static int tas5720_suspend(struct snd_soc_codec *codec)
{
struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec);
int ret;
regcache_cache_only(tas5720->regmap, true);
regcache_mark_dirty(tas5720->regmap);
ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies),
tas5720->supplies);
if (ret < 0)
dev_err(codec->dev, "failed to disable supplies: %d\n", ret);
return ret;
}
static int tas5720_resume(struct snd_soc_codec *codec)
{
struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec);
int ret;
ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies),
tas5720->supplies);
if (ret < 0) {
dev_err(codec->dev, "failed to enable supplies: %d\n", ret);
return ret;
}
regcache_cache_only(tas5720->regmap, false);
ret = regcache_sync(tas5720->regmap);
if (ret < 0) {
dev_err(codec->dev, "failed to sync regcache: %d\n", ret);
return ret;
}
return 0;
}
#else
#define tas5720_suspend NULL
#define tas5720_resume NULL
#endif
static bool tas5720_is_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TAS5720_DEVICE_ID_REG:
case TAS5720_FAULT_REG:
return true;
default:
return false;
}
}
static const struct regmap_config tas5720_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = TAS5720_MAX_REG,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = tas5720_is_volatile_reg,
};
/*
* DAC analog gain. There are four discrete values to select from, ranging
* from 19.2 dB to 26.3dB.
*/
static const DECLARE_TLV_DB_RANGE(dac_analog_tlv,
0x0, 0x0, TLV_DB_SCALE_ITEM(1920, 0, 0),
0x1, 0x1, TLV_DB_SCALE_ITEM(2070, 0, 0),
0x2, 0x2, TLV_DB_SCALE_ITEM(2350, 0, 0),
0x3, 0x3, TLV_DB_SCALE_ITEM(2630, 0, 0),
);
/*
* DAC digital volumes. From -103.5 to 24 dB in 0.5 dB steps. Note that
* setting the gain below -100 dB (register value <0x7) is effectively a MUTE
* as per device datasheet.
*/
static DECLARE_TLV_DB_SCALE(dac_tlv, -10350, 50, 0);
static const struct snd_kcontrol_new tas5720_snd_controls[] = {
SOC_SINGLE_TLV("Speaker Driver Playback Volume",
TAS5720_VOLUME_CTRL_REG, 0, 0xff, 0, dac_tlv),
SOC_SINGLE_TLV("Speaker Driver Analog Gain", TAS5720_ANALOG_CTRL_REG,
TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv),
};
static const struct snd_soc_dapm_widget tas5720_dapm_widgets[] = {
SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas5720_dac_event,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_OUTPUT("OUT")
};
static const struct snd_soc_dapm_route tas5720_audio_map[] = {
{ "DAC", NULL, "DAC IN" },
{ "OUT", NULL, "DAC" },
};
static struct snd_soc_codec_driver soc_codec_dev_tas5720 = {
.probe = tas5720_codec_probe,
.remove = tas5720_codec_remove,
.suspend = tas5720_suspend,
.resume = tas5720_resume,
.controls = tas5720_snd_controls,
.num_controls = ARRAY_SIZE(tas5720_snd_controls),
.dapm_widgets = tas5720_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets),
.dapm_routes = tas5720_audio_map,
.num_dapm_routes = ARRAY_SIZE(tas5720_audio_map),
};
/* PCM rates supported by the TAS5720 driver */
#define TAS5720_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
/* Formats supported by TAS5720 driver */
#define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE)
static struct snd_soc_dai_ops tas5720_speaker_dai_ops = {
.hw_params = tas5720_hw_params,
.set_fmt = tas5720_set_dai_fmt,
.set_tdm_slot = tas5720_set_dai_tdm_slot,
.digital_mute = tas5720_mute,
};
/*
* TAS5720 DAI structure
*
* Note that were are advertising .playback.channels_max = 2 despite this being
* a mono amplifier. The reason for that is that some serial ports such as TI's
* McASP module have a minimum number of channels (2) that they can output.
* Advertising more channels than we have will allow us to interface with such
* a serial port without really any negative side effects as the TAS5720 will
* simply ignore any extra channel(s) asides from the one channel that is
* configured to be played back.
*/
static struct snd_soc_dai_driver tas5720_dai[] = {
{
.name = "tas5720-amplifier",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = TAS5720_RATES,
.formats = TAS5720_FORMATS,
},
.ops = &tas5720_speaker_dai_ops,
},
};
static int tas5720_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct tas5720_data *data;
int ret;
int i;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->tas5720_client = client;
data->regmap = devm_regmap_init_i2c(client, &tas5720_regmap_config);
if (IS_ERR(data->regmap)) {
ret = PTR_ERR(data->regmap);
dev_err(dev, "failed to allocate register map: %d\n", ret);
return ret;
}
for (i = 0; i < ARRAY_SIZE(data->supplies); i++)
data->supplies[i].supply = tas5720_supply_names[i];
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies),
data->supplies);
if (ret != 0) {
dev_err(dev, "failed to request supplies: %d\n", ret);
return ret;
}
dev_set_drvdata(dev, data);
ret = snd_soc_register_codec(&client->dev,
&soc_codec_dev_tas5720,
tas5720_dai, ARRAY_SIZE(tas5720_dai));
if (ret < 0) {
dev_err(dev, "failed to register codec: %d\n", ret);
return ret;
}
return 0;
}
static int tas5720_remove(struct i2c_client *client)
{
struct device *dev = &client->dev;
snd_soc_unregister_codec(dev);
return 0;
}
static const struct i2c_device_id tas5720_id[] = {
{ "tas5720", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tas5720_id);
#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id tas5720_of_match[] = {
{ .compatible = "ti,tas5720", },
{ },
};
MODULE_DEVICE_TABLE(of, tas5720_of_match);
#endif
static struct i2c_driver tas5720_i2c_driver = {
.driver = {
.name = "tas5720",
.of_match_table = of_match_ptr(tas5720_of_match),
},
.probe = tas5720_probe,
.remove = tas5720_remove,
.id_table = tas5720_id,
};
module_i2c_driver(tas5720_i2c_driver);
MODULE_AUTHOR("Andreas Dannenberg <dannenberg@ti.com>");
MODULE_DESCRIPTION("TAS5720 Audio amplifier driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,90 @@
/*
* tas5720.h - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier
*
* Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com
*
* Author: Andreas Dannenberg <dannenberg@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#ifndef __TAS5720_H__
#define __TAS5720_H__
/* Register Address Map */
#define TAS5720_DEVICE_ID_REG 0x00
#define TAS5720_POWER_CTRL_REG 0x01
#define TAS5720_DIGITAL_CTRL1_REG 0x02
#define TAS5720_DIGITAL_CTRL2_REG 0x03
#define TAS5720_VOLUME_CTRL_REG 0x04
#define TAS5720_ANALOG_CTRL_REG 0x06
#define TAS5720_FAULT_REG 0x08
#define TAS5720_DIGITAL_CLIP2_REG 0x10
#define TAS5720_DIGITAL_CLIP1_REG 0x11
#define TAS5720_MAX_REG TAS5720_DIGITAL_CLIP1_REG
/* TAS5720_DEVICE_ID_REG */
#define TAS5720_DEVICE_ID 0x01
/* TAS5720_POWER_CTRL_REG */
#define TAS5720_DIG_CLIP_MASK GENMASK(7, 2)
#define TAS5720_SLEEP BIT(1)
#define TAS5720_SDZ BIT(0)
/* TAS5720_DIGITAL_CTRL1_REG */
#define TAS5720_HPF_BYPASS BIT(7)
#define TAS5720_TDM_CFG_SRC BIT(6)
#define TAS5720_SSZ_DS BIT(3)
#define TAS5720_SAIF_RIGHTJ_24BIT (0x0)
#define TAS5720_SAIF_RIGHTJ_20BIT (0x1)
#define TAS5720_SAIF_RIGHTJ_18BIT (0x2)
#define TAS5720_SAIF_RIGHTJ_16BIT (0x3)
#define TAS5720_SAIF_I2S (0x4)
#define TAS5720_SAIF_LEFTJ (0x5)
#define TAS5720_SAIF_FORMAT_MASK GENMASK(2, 0)
/* TAS5720_DIGITAL_CTRL2_REG */
#define TAS5720_MUTE BIT(4)
#define TAS5720_TDM_SLOT_SEL_MASK GENMASK(2, 0)
/* TAS5720_ANALOG_CTRL_REG */
#define TAS5720_PWM_RATE_6_3_FSYNC (0x0 << 4)
#define TAS5720_PWM_RATE_8_4_FSYNC (0x1 << 4)
#define TAS5720_PWM_RATE_10_5_FSYNC (0x2 << 4)
#define TAS5720_PWM_RATE_12_6_FSYNC (0x3 << 4)
#define TAS5720_PWM_RATE_14_7_FSYNC (0x4 << 4)
#define TAS5720_PWM_RATE_16_8_FSYNC (0x5 << 4)
#define TAS5720_PWM_RATE_20_10_FSYNC (0x6 << 4)
#define TAS5720_PWM_RATE_24_12_FSYNC (0x7 << 4)
#define TAS5720_PWM_RATE_MASK GENMASK(6, 4)
#define TAS5720_ANALOG_GAIN_19_2DBV (0x0 << 2)
#define TAS5720_ANALOG_GAIN_20_7DBV (0x1 << 2)
#define TAS5720_ANALOG_GAIN_23_5DBV (0x2 << 2)
#define TAS5720_ANALOG_GAIN_26_3DBV (0x3 << 2)
#define TAS5720_ANALOG_GAIN_MASK GENMASK(3, 2)
#define TAS5720_ANALOG_GAIN_SHIFT (0x2)
/* TAS5720_FAULT_REG */
#define TAS5720_OC_THRESH_100PCT (0x0 << 4)
#define TAS5720_OC_THRESH_75PCT (0x1 << 4)
#define TAS5720_OC_THRESH_50PCT (0x2 << 4)
#define TAS5720_OC_THRESH_25PCT (0x3 << 4)
#define TAS5720_OC_THRESH_MASK GENMASK(5, 4)
#define TAS5720_CLKE BIT(3)
#define TAS5720_OCE BIT(2)
#define TAS5720_DCE BIT(1)
#define TAS5720_OTE BIT(0)
#define TAS5720_FAULT_MASK GENMASK(3, 0)
/* TAS5720_DIGITAL_CLIP1_REG */
#define TAS5720_CLIP1_MASK GENMASK(7, 2)
#define TAS5720_CLIP1_SHIFT (0x2)
#endif /* __TAS5720_H__ */

View file

@ -28,6 +28,7 @@
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/acpi.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
@ -1280,10 +1281,19 @@ static const struct i2c_device_id aic31xx_i2c_id[] = {
};
MODULE_DEVICE_TABLE(i2c, aic31xx_i2c_id);
#ifdef CONFIG_ACPI
static const struct acpi_device_id aic31xx_acpi_match[] = {
{ "10TI3100", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, aic31xx_acpi_match);
#endif
static struct i2c_driver aic31xx_i2c_driver = {
.driver = {
.name = "tlv320aic31xx-codec",
.of_match_table = of_match_ptr(tlv320aic31xx_of_match),
.acpi_match_table = ACPI_PTR(aic31xx_acpi_match),
},
.probe = aic31xx_i2c_probe,
.remove = aic31xx_i2c_remove,

View file

@ -0,0 +1,74 @@
/*
* linux/sound/soc/codecs/tlv320aic32x4-i2c.c
*
* Copyright 2011 NW Digital Radio
*
* Author: Jeremy McDermond <nh6z@nh6z.net>
*
* Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "tlv320aic32x4.h"
static int aic32x4_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct regmap *regmap;
struct regmap_config config;
config = aic32x4_regmap_config;
config.reg_bits = 8;
config.val_bits = 8;
regmap = devm_regmap_init_i2c(i2c, &config);
return aic32x4_probe(&i2c->dev, regmap);
}
static int aic32x4_i2c_remove(struct i2c_client *i2c)
{
return aic32x4_remove(&i2c->dev);
}
static const struct i2c_device_id aic32x4_i2c_id[] = {
{ "tlv320aic32x4", 0 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id);
static const struct of_device_id aic32x4_of_id[] = {
{ .compatible = "ti,tlv320aic32x4", },
{ /* senitel */ }
};
MODULE_DEVICE_TABLE(of, aic32x4_of_id);
static struct i2c_driver aic32x4_i2c_driver = {
.driver = {
.name = "tlv320aic32x4",
.of_match_table = aic32x4_of_id,
},
.probe = aic32x4_i2c_probe,
.remove = aic32x4_i2c_remove,
.id_table = aic32x4_i2c_id,
};
module_i2c_driver(aic32x4_i2c_driver);
MODULE_DESCRIPTION("ASoC TLV320AIC32x4 codec driver I2C");
MODULE_AUTHOR("Jeremy McDermond <nh6z@nh6z.net>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,76 @@
/*
* linux/sound/soc/codecs/tlv320aic32x4-spi.c
*
* Copyright 2011 NW Digital Radio
*
* Author: Jeremy McDermond <nh6z@nh6z.net>
*
* Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/spi/spi.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <sound/soc.h>
#include "tlv320aic32x4.h"
static int aic32x4_spi_probe(struct spi_device *spi)
{
struct regmap *regmap;
struct regmap_config config;
config = aic32x4_regmap_config;
config.reg_bits = 7;
config.pad_bits = 1;
config.val_bits = 8;
config.read_flag_mask = 0x01;
regmap = devm_regmap_init_spi(spi, &config);
return aic32x4_probe(&spi->dev, regmap);
}
static int aic32x4_spi_remove(struct spi_device *spi)
{
return aic32x4_remove(&spi->dev);
}
static const struct spi_device_id aic32x4_spi_id[] = {
{ "tlv320aic32x4", 0 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, aic32x4_spi_id);
static const struct of_device_id aic32x4_of_id[] = {
{ .compatible = "ti,tlv320aic32x4", },
{ /* senitel */ }
};
MODULE_DEVICE_TABLE(of, aic32x4_of_id);
static struct spi_driver aic32x4_spi_driver = {
.driver = {
.name = "tlv320aic32x4",
.owner = THIS_MODULE,
.of_match_table = aic32x4_of_id,
},
.probe = aic32x4_spi_probe,
.remove = aic32x4_spi_remove,
.id_table = aic32x4_spi_id,
};
module_spi_driver(aic32x4_spi_driver);
MODULE_DESCRIPTION("ASoC TLV320AIC32x4 codec driver SPI");
MODULE_AUTHOR("Jeremy McDermond <nh6z@nh6z.net>");
MODULE_LICENSE("GPL");

View file

@ -30,7 +30,6 @@
#include <linux/pm.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/clk.h>
@ -160,7 +159,10 @@ static const struct aic32x4_rate_divs aic32x4_divs[] = {
/* 48k rate */
{AIC32X4_FREQ_12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4},
{AIC32X4_FREQ_24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4},
{AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}
{AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4},
/* 96k rate */
{AIC32X4_FREQ_25000000, 96000, 2, 7, 8643, 64, 4, 4, 64, 4, 4, 1},
};
static const struct snd_kcontrol_new hpl_output_mixer_controls[] = {
@ -181,16 +183,71 @@ static const struct snd_kcontrol_new lor_output_mixer_controls[] = {
SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0),
};
static const struct snd_kcontrol_new left_input_mixer_controls[] = {
SOC_DAPM_SINGLE("IN1_L P Switch", AIC32X4_LMICPGAPIN, 6, 1, 0),
SOC_DAPM_SINGLE("IN2_L P Switch", AIC32X4_LMICPGAPIN, 4, 1, 0),
SOC_DAPM_SINGLE("IN3_L P Switch", AIC32X4_LMICPGAPIN, 2, 1, 0),
static const char * const resistor_text[] = {
"Off", "10 kOhm", "20 kOhm", "40 kOhm",
};
static const struct snd_kcontrol_new right_input_mixer_controls[] = {
SOC_DAPM_SINGLE("IN1_R P Switch", AIC32X4_RMICPGAPIN, 6, 1, 0),
SOC_DAPM_SINGLE("IN2_R P Switch", AIC32X4_RMICPGAPIN, 4, 1, 0),
SOC_DAPM_SINGLE("IN3_R P Switch", AIC32X4_RMICPGAPIN, 2, 1, 0),
/* Left mixer pins */
static SOC_ENUM_SINGLE_DECL(in1l_lpga_p_enum, AIC32X4_LMICPGAPIN, 6, resistor_text);
static SOC_ENUM_SINGLE_DECL(in2l_lpga_p_enum, AIC32X4_LMICPGAPIN, 4, resistor_text);
static SOC_ENUM_SINGLE_DECL(in3l_lpga_p_enum, AIC32X4_LMICPGAPIN, 2, resistor_text);
static SOC_ENUM_SINGLE_DECL(in1r_lpga_p_enum, AIC32X4_LMICPGAPIN, 0, resistor_text);
static SOC_ENUM_SINGLE_DECL(cml_lpga_n_enum, AIC32X4_LMICPGANIN, 6, resistor_text);
static SOC_ENUM_SINGLE_DECL(in2r_lpga_n_enum, AIC32X4_LMICPGANIN, 4, resistor_text);
static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_text);
static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = {
SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum),
};
static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = {
SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum),
};
static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = {
SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum),
};
static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = {
SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum),
};
static const struct snd_kcontrol_new cml_to_lmixer_controls[] = {
SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum),
};
static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = {
SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum),
};
static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = {
SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum),
};
/* Right mixer pins */
static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, resistor_text);
static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, resistor_text);
static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, resistor_text);
static SOC_ENUM_SINGLE_DECL(in2l_rpga_p_enum, AIC32X4_RMICPGAPIN, 0, resistor_text);
static SOC_ENUM_SINGLE_DECL(cmr_rpga_n_enum, AIC32X4_RMICPGANIN, 6, resistor_text);
static SOC_ENUM_SINGLE_DECL(in1l_rpga_n_enum, AIC32X4_RMICPGANIN, 4, resistor_text);
static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_text);
static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = {
SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum),
};
static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = {
SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum),
};
static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = {
SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum),
};
static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = {
SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum),
};
static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = {
SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum),
};
static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = {
SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum),
};
static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = {
SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum),
};
static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = {
@ -214,14 +271,39 @@ static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = {
&lor_output_mixer_controls[0],
ARRAY_SIZE(lor_output_mixer_controls)),
SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0),
SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0,
&left_input_mixer_controls[0],
ARRAY_SIZE(left_input_mixer_controls)),
SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0,
&right_input_mixer_controls[0],
ARRAY_SIZE(right_input_mixer_controls)),
SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0),
SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0),
SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0,
in1r_to_rmixer_controls),
SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0,
in2r_to_rmixer_controls),
SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0,
in3r_to_rmixer_controls),
SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0,
in2l_to_rmixer_controls),
SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0,
cmr_to_rmixer_controls),
SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0,
in1l_to_rmixer_controls),
SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0,
in3l_to_rmixer_controls),
SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0),
SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0,
in1l_to_lmixer_controls),
SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0,
in2l_to_lmixer_controls),
SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0,
in3l_to_lmixer_controls),
SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0,
in1r_to_lmixer_controls),
SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0,
cml_to_lmixer_controls),
SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0,
in2r_to_lmixer_controls),
SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0,
in3r_to_lmixer_controls),
SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0),
SND_SOC_DAPM_OUTPUT("HPL"),
@ -261,19 +343,77 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = {
{"LOR Power", NULL, "LOR Output Mixer"},
{"LOR", NULL, "LOR Power"},
/* Left input */
{"Left Input Mixer", "IN1_L P Switch", "IN1_L"},
{"Left Input Mixer", "IN2_L P Switch", "IN2_L"},
{"Left Input Mixer", "IN3_L P Switch", "IN3_L"},
{"Left ADC", NULL, "Left Input Mixer"},
/* Right Input */
{"Right Input Mixer", "IN1_R P Switch", "IN1_R"},
{"Right Input Mixer", "IN2_R P Switch", "IN2_R"},
{"Right Input Mixer", "IN3_R P Switch", "IN3_R"},
{"Right ADC", NULL, "IN1_R to Right Mixer Positive Resistor"},
{"IN1_R to Right Mixer Positive Resistor", "10 kOhm", "IN1_R"},
{"IN1_R to Right Mixer Positive Resistor", "20 kOhm", "IN1_R"},
{"IN1_R to Right Mixer Positive Resistor", "40 kOhm", "IN1_R"},
{"Right ADC", NULL, "Right Input Mixer"},
{"Right ADC", NULL, "IN2_R to Right Mixer Positive Resistor"},
{"IN2_R to Right Mixer Positive Resistor", "10 kOhm", "IN2_R"},
{"IN2_R to Right Mixer Positive Resistor", "20 kOhm", "IN2_R"},
{"IN2_R to Right Mixer Positive Resistor", "40 kOhm", "IN2_R"},
{"Right ADC", NULL, "IN3_R to Right Mixer Positive Resistor"},
{"IN3_R to Right Mixer Positive Resistor", "10 kOhm", "IN3_R"},
{"IN3_R to Right Mixer Positive Resistor", "20 kOhm", "IN3_R"},
{"IN3_R to Right Mixer Positive Resistor", "40 kOhm", "IN3_R"},
{"Right ADC", NULL, "IN2_L to Right Mixer Positive Resistor"},
{"IN2_L to Right Mixer Positive Resistor", "10 kOhm", "IN2_L"},
{"IN2_L to Right Mixer Positive Resistor", "20 kOhm", "IN2_L"},
{"IN2_L to Right Mixer Positive Resistor", "40 kOhm", "IN2_L"},
{"Right ADC", NULL, "CM_R to Right Mixer Negative Resistor"},
{"CM_R to Right Mixer Negative Resistor", "10 kOhm", "CM_R"},
{"CM_R to Right Mixer Negative Resistor", "20 kOhm", "CM_R"},
{"CM_R to Right Mixer Negative Resistor", "40 kOhm", "CM_R"},
{"Right ADC", NULL, "IN1_L to Right Mixer Negative Resistor"},
{"IN1_L to Right Mixer Negative Resistor", "10 kOhm", "IN1_L"},
{"IN1_L to Right Mixer Negative Resistor", "20 kOhm", "IN1_L"},
{"IN1_L to Right Mixer Negative Resistor", "40 kOhm", "IN1_L"},
{"Right ADC", NULL, "IN3_L to Right Mixer Negative Resistor"},
{"IN3_L to Right Mixer Negative Resistor", "10 kOhm", "IN3_L"},
{"IN3_L to Right Mixer Negative Resistor", "20 kOhm", "IN3_L"},
{"IN3_L to Right Mixer Negative Resistor", "40 kOhm", "IN3_L"},
/* Left Input */
{"Left ADC", NULL, "IN1_L to Left Mixer Positive Resistor"},
{"IN1_L to Left Mixer Positive Resistor", "10 kOhm", "IN1_L"},
{"IN1_L to Left Mixer Positive Resistor", "20 kOhm", "IN1_L"},
{"IN1_L to Left Mixer Positive Resistor", "40 kOhm", "IN1_L"},
{"Left ADC", NULL, "IN2_L to Left Mixer Positive Resistor"},
{"IN2_L to Left Mixer Positive Resistor", "10 kOhm", "IN2_L"},
{"IN2_L to Left Mixer Positive Resistor", "20 kOhm", "IN2_L"},
{"IN2_L to Left Mixer Positive Resistor", "40 kOhm", "IN2_L"},
{"Left ADC", NULL, "IN3_L to Left Mixer Positive Resistor"},
{"IN3_L to Left Mixer Positive Resistor", "10 kOhm", "IN3_L"},
{"IN3_L to Left Mixer Positive Resistor", "20 kOhm", "IN3_L"},
{"IN3_L to Left Mixer Positive Resistor", "40 kOhm", "IN3_L"},
{"Left ADC", NULL, "IN1_R to Left Mixer Positive Resistor"},
{"IN1_R to Left Mixer Positive Resistor", "10 kOhm", "IN1_R"},
{"IN1_R to Left Mixer Positive Resistor", "20 kOhm", "IN1_R"},
{"IN1_R to Left Mixer Positive Resistor", "40 kOhm", "IN1_R"},
{"Left ADC", NULL, "CM_L to Left Mixer Negative Resistor"},
{"CM_L to Left Mixer Negative Resistor", "10 kOhm", "CM_L"},
{"CM_L to Left Mixer Negative Resistor", "20 kOhm", "CM_L"},
{"CM_L to Left Mixer Negative Resistor", "40 kOhm", "CM_L"},
{"Left ADC", NULL, "IN2_R to Left Mixer Negative Resistor"},
{"IN2_R to Left Mixer Negative Resistor", "10 kOhm", "IN2_R"},
{"IN2_R to Left Mixer Negative Resistor", "20 kOhm", "IN2_R"},
{"IN2_R to Left Mixer Negative Resistor", "40 kOhm", "IN2_R"},
{"Left ADC", NULL, "IN3_R to Left Mixer Negative Resistor"},
{"IN3_R to Left Mixer Negative Resistor", "10 kOhm", "IN3_R"},
{"IN3_R to Left Mixer Negative Resistor", "20 kOhm", "IN3_R"},
{"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"},
};
static const struct regmap_range_cfg aic32x4_regmap_pages[] = {
@ -287,14 +427,12 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = {
},
};
static const struct regmap_config aic32x4_regmap = {
.reg_bits = 8,
.val_bits = 8,
const struct regmap_config aic32x4_regmap_config = {
.max_register = AIC32X4_RMICPGAVOL,
.ranges = aic32x4_regmap_pages,
.num_ranges = ARRAY_SIZE(aic32x4_regmap_pages),
};
EXPORT_SYMBOL(aic32x4_regmap_config);
static inline int aic32x4_get_divs(int mclk, int rate)
{
@ -567,7 +705,7 @@ static int aic32x4_set_bias_level(struct snd_soc_codec *codec,
return 0;
}
#define AIC32X4_RATES SNDRV_PCM_RATE_8000_48000
#define AIC32X4_RATES SNDRV_PCM_RATE_8000_96000
#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \
| SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
@ -596,7 +734,7 @@ static struct snd_soc_dai_driver aic32x4_dai = {
.symmetric_rates = 1,
};
static int aic32x4_probe(struct snd_soc_codec *codec)
static int aic32x4_codec_probe(struct snd_soc_codec *codec)
{
struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
u32 tmp_reg;
@ -655,7 +793,7 @@ static int aic32x4_probe(struct snd_soc_codec *codec)
}
static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = {
.probe = aic32x4_probe,
.probe = aic32x4_codec_probe,
.set_bias_level = aic32x4_set_bias_level,
.suspend_bias_off = true,
@ -777,24 +915,22 @@ static int aic32x4_setup_regulators(struct device *dev,
return ret;
}
static int aic32x4_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
int aic32x4_probe(struct device *dev, struct regmap *regmap)
{
struct aic32x4_pdata *pdata = i2c->dev.platform_data;
struct aic32x4_priv *aic32x4;
struct device_node *np = i2c->dev.of_node;
struct aic32x4_pdata *pdata = dev->platform_data;
struct device_node *np = dev->of_node;
int ret;
aic32x4 = devm_kzalloc(&i2c->dev, sizeof(struct aic32x4_priv),
if (IS_ERR(regmap))
return PTR_ERR(regmap);
aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv),
GFP_KERNEL);
if (aic32x4 == NULL)
return -ENOMEM;
aic32x4->regmap = devm_regmap_init_i2c(i2c, &aic32x4_regmap);
if (IS_ERR(aic32x4->regmap))
return PTR_ERR(aic32x4->regmap);
i2c_set_clientdata(i2c, aic32x4);
dev_set_drvdata(dev, aic32x4);
if (pdata) {
aic32x4->power_cfg = pdata->power_cfg;
@ -804,7 +940,7 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c,
} else if (np) {
ret = aic32x4_parse_dt(aic32x4, np);
if (ret) {
dev_err(&i2c->dev, "Failed to parse DT node\n");
dev_err(dev, "Failed to parse DT node\n");
return ret;
}
} else {
@ -814,71 +950,48 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c,
aic32x4->rstn_gpio = -1;
}
aic32x4->mclk = devm_clk_get(&i2c->dev, "mclk");
aic32x4->mclk = devm_clk_get(dev, "mclk");
if (IS_ERR(aic32x4->mclk)) {
dev_err(&i2c->dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n");
dev_err(dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n");
return PTR_ERR(aic32x4->mclk);
}
if (gpio_is_valid(aic32x4->rstn_gpio)) {
ret = devm_gpio_request_one(&i2c->dev, aic32x4->rstn_gpio,
ret = devm_gpio_request_one(dev, aic32x4->rstn_gpio,
GPIOF_OUT_INIT_LOW, "tlv320aic32x4 rstn");
if (ret != 0)
return ret;
}
ret = aic32x4_setup_regulators(&i2c->dev, aic32x4);
ret = aic32x4_setup_regulators(dev, aic32x4);
if (ret) {
dev_err(&i2c->dev, "Failed to setup regulators\n");
dev_err(dev, "Failed to setup regulators\n");
return ret;
}
ret = snd_soc_register_codec(&i2c->dev,
ret = snd_soc_register_codec(dev,
&soc_codec_dev_aic32x4, &aic32x4_dai, 1);
if (ret) {
dev_err(&i2c->dev, "Failed to register codec\n");
dev_err(dev, "Failed to register codec\n");
aic32x4_disable_regulators(aic32x4);
return ret;
}
i2c_set_clientdata(i2c, aic32x4);
return 0;
}
EXPORT_SYMBOL(aic32x4_probe);
static int aic32x4_i2c_remove(struct i2c_client *client)
int aic32x4_remove(struct device *dev)
{
struct aic32x4_priv *aic32x4 = i2c_get_clientdata(client);
struct aic32x4_priv *aic32x4 = dev_get_drvdata(dev);
aic32x4_disable_regulators(aic32x4);
snd_soc_unregister_codec(&client->dev);
snd_soc_unregister_codec(dev);
return 0;
}
static const struct i2c_device_id aic32x4_i2c_id[] = {
{ "tlv320aic32x4", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id);
static const struct of_device_id aic32x4_of_id[] = {
{ .compatible = "ti,tlv320aic32x4", },
{ /* senitel */ }
};
MODULE_DEVICE_TABLE(of, aic32x4_of_id);
static struct i2c_driver aic32x4_i2c_driver = {
.driver = {
.name = "tlv320aic32x4",
.of_match_table = aic32x4_of_id,
},
.probe = aic32x4_i2c_probe,
.remove = aic32x4_i2c_remove,
.id_table = aic32x4_i2c_id,
};
module_i2c_driver(aic32x4_i2c_driver);
EXPORT_SYMBOL(aic32x4_remove);
MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver");
MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>");

View file

@ -10,6 +10,13 @@
#ifndef _TLV320AIC32X4_H
#define _TLV320AIC32X4_H
struct device;
struct regmap_config;
extern const struct regmap_config aic32x4_regmap_config;
int aic32x4_probe(struct device *dev, struct regmap *regmap);
int aic32x4_remove(struct device *dev);
/* tlv320aic32x4 register space (in decimal to match datasheet) */
#define AIC32X4_PAGE1 128

View file

@ -824,7 +824,7 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec,
{
struct twl6040 *twl6040 = codec->control_data;
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
int ret;
int ret = 0;
switch (level) {
case SND_SOC_BIAS_ON:
@ -832,12 +832,16 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec,
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
if (priv->codec_powered)
if (priv->codec_powered) {
/* Select low power PLL in standby */
ret = twl6040_set_pll(twl6040, TWL6040_SYSCLK_SEL_LPPLL,
32768, 19200000);
break;
}
ret = twl6040_power(twl6040, 1);
if (ret)
return ret;
break;
priv->codec_powered = 1;
@ -853,7 +857,7 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec,
break;
}
return 0;
return ret;
}
static int twl6040_startup(struct snd_pcm_substream *substream,
@ -983,9 +987,9 @@ static void twl6040_mute_path(struct snd_soc_codec *codec, enum twl6040_dai_id i
if (mute) {
/* Power down drivers and DACs */
hflctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA |
TWL6040_HFDRVENA);
TWL6040_HFDRVENA | TWL6040_HFSWENA);
hfrctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA |
TWL6040_HFDRVENA);
TWL6040_HFDRVENA | TWL6040_HFSWENA);
}
twl6040_reg_write(twl6040, TWL6040_REG_HFLCTL, hflctl);

View file

@ -3793,9 +3793,8 @@ static int wm8962_runtime_resume(struct device *dev)
ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies),
wm8962->supplies);
if (ret != 0) {
dev_err(dev,
"Failed to enable supplies: %d\n", ret);
return ret;
dev_err(dev, "Failed to enable supplies: %d\n", ret);
goto disable_clock;
}
regcache_cache_only(wm8962->regmap, false);
@ -3833,6 +3832,10 @@ static int wm8962_runtime_resume(struct device *dev)
msleep(5);
return 0;
disable_clock:
clk_disable_unprepare(wm8962->pdata.mclk);
return ret;
}
static int wm8962_runtime_suspend(struct device *dev)

View file

@ -16,9 +16,9 @@
#include <asm/types.h>
#include <sound/soc.h>
#define WM8962_SYSCLK_MCLK 1
#define WM8962_SYSCLK_FLL 2
#define WM8962_SYSCLK_PLL3 3
#define WM8962_SYSCLK_MCLK 0
#define WM8962_SYSCLK_FLL 1
#define WM8962_SYSCLK_PLL3 2
#define WM8962_FLL 1

View file

@ -643,6 +643,7 @@ MODULE_DEVICE_TABLE(of, asoc_simple_of_match);
static struct platform_driver asoc_simple_card = {
.driver = {
.name = "asoc-simple-card",
.pm = &snd_soc_pm_ops,
.of_match_table = asoc_simple_of_match,
},
.probe = asoc_simple_card_probe,

View file

@ -1,6 +1,7 @@
config SND_KIRKWOOD_SOC
tristate "SoC Audio for the Marvell Kirkwood and Dove chips"
depends on ARCH_DOVE || ARCH_MVEBU || COMPILE_TEST
depends on HAS_DMA
help
Say Y or M if you want to add support for codecs attached to
the Kirkwood I2S interface. You will also need to select the

View file

@ -43,6 +43,7 @@ config SND_SOC_MT8173_RT5650_RT5676
depends on SND_SOC_MEDIATEK && I2C
select SND_SOC_RT5645
select SND_SOC_RT5677
select SND_SOC_HDMI_CODEC
help
This adds ASoC driver for Mediatek MT8173 boards
with the RT5650 and RT5676 codecs.

View file

@ -134,7 +134,9 @@ static struct snd_soc_dai_link_component mt8173_rt5650_rt5676_codecs[] = {
enum {
DAI_LINK_PLAYBACK,
DAI_LINK_CAPTURE,
DAI_LINK_HDMI,
DAI_LINK_CODEC_I2S,
DAI_LINK_HDMI_I2S,
DAI_LINK_INTERCODEC
};
@ -161,6 +163,16 @@ static struct snd_soc_dai_link mt8173_rt5650_rt5676_dais[] = {
.dynamic = 1,
.dpcm_capture = 1,
},
[DAI_LINK_HDMI] = {
.name = "HDMI",
.stream_name = "HDMI PCM",
.cpu_dai_name = "HDMI",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
.dynamic = 1,
.dpcm_playback = 1,
},
/* Back End DAI links */
[DAI_LINK_CODEC_I2S] = {
@ -177,6 +189,13 @@ static struct snd_soc_dai_link mt8173_rt5650_rt5676_dais[] = {
.dpcm_playback = 1,
.dpcm_capture = 1,
},
[DAI_LINK_HDMI_I2S] = {
.name = "HDMI BE",
.cpu_dai_name = "HDMIO",
.no_pcm = 1,
.codec_dai_name = "i2s-hifi",
.dpcm_playback = 1,
},
/* rt5676 <-> rt5650 intercodec link: Sets rt5676 I2S2 as master */
[DAI_LINK_INTERCODEC] = {
.name = "rt5650_rt5676 intercodec",
@ -251,6 +270,14 @@ static int mt8173_rt5650_rt5676_dev_probe(struct platform_device *pdev)
mt8173_rt5650_rt5676_dais[DAI_LINK_INTERCODEC].codec_of_node =
mt8173_rt5650_rt5676_codecs[1].of_node;
mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node =
of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 2);
if (!mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node) {
dev_err(&pdev->dev,
"Property 'audio-codec' missing or invalid\n");
return -EINVAL;
}
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);

View file

@ -85,12 +85,29 @@ static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime)
{
struct snd_soc_card *card = runtime->card;
struct snd_soc_codec *codec = runtime->codec_dais[0]->codec;
const char *codec_capture_dai = runtime->codec_dais[1]->name;
int ret;
rt5645_sel_asrc_clk_src(codec,
RT5645_DA_STEREO_FILTER |
RT5645_AD_STEREO_FILTER,
RT5645_DA_STEREO_FILTER,
RT5645_CLK_SEL_I2S1_ASRC);
if (!strcmp(codec_capture_dai, "rt5645-aif1")) {
rt5645_sel_asrc_clk_src(codec,
RT5645_AD_STEREO_FILTER,
RT5645_CLK_SEL_I2S1_ASRC);
} else if (!strcmp(codec_capture_dai, "rt5645-aif2")) {
rt5645_sel_asrc_clk_src(codec,
RT5645_AD_STEREO_FILTER,
RT5645_CLK_SEL_I2S2_ASRC);
} else {
dev_warn(card->dev,
"Only one dai codec found in DTS, enabled rt5645 AD filter\n");
rt5645_sel_asrc_clk_src(codec,
RT5645_AD_STEREO_FILTER,
RT5645_CLK_SEL_I2S1_ASRC);
}
/* enable jack detection */
ret = snd_soc_card_jack_new(card, "Headset Jack",
SND_JACK_HEADPHONE | SND_JACK_MICROPHONE |
@ -110,6 +127,11 @@ static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime)
static struct snd_soc_dai_link_component mt8173_rt5650_codecs[] = {
{
/* Playback */
.dai_name = "rt5645-aif1",
},
{
/* Capture */
.dai_name = "rt5645-aif1",
},
};
@ -149,7 +171,7 @@ static struct snd_soc_dai_link mt8173_rt5650_dais[] = {
.cpu_dai_name = "I2S",
.no_pcm = 1,
.codecs = mt8173_rt5650_codecs,
.num_codecs = 1,
.num_codecs = 2,
.init = mt8173_rt5650_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
@ -177,6 +199,8 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &mt8173_rt5650_card;
struct device_node *platform_node;
struct device_node *np;
const char *codec_capture_dai;
int i, ret;
platform_node = of_parse_phandle(pdev->dev.of_node,
@ -199,6 +223,26 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev)
"Property 'audio-codec' missing or invalid\n");
return -EINVAL;
}
mt8173_rt5650_codecs[1].of_node = mt8173_rt5650_codecs[0].of_node;
if (of_find_node_by_name(platform_node, "codec-capture")) {
np = of_get_child_by_name(pdev->dev.of_node, "codec-capture");
if (!np) {
dev_err(&pdev->dev,
"%s: Can't find codec-capture DT node\n",
__func__);
return -EINVAL;
}
ret = snd_soc_of_get_dai_name(np, &codec_capture_dai);
if (ret < 0) {
dev_err(&pdev->dev,
"%s codec_capture_dai name fail %d\n",
__func__, ret);
return ret;
}
mt8173_rt5650_codecs[1].dai_name = codec_capture_dai;
}
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);

View file

@ -123,6 +123,7 @@
#define AFE_TDM_CON1_WLEN_32BIT (0x2 << 8)
#define AFE_TDM_CON1_MSB_ALIGNED (0x1 << 4)
#define AFE_TDM_CON1_1_BCK_DELAY (0x1 << 3)
#define AFE_TDM_CON1_LRCK_INV (0x1 << 2)
#define AFE_TDM_CON1_BCK_INV (0x1 << 1)
#define AFE_TDM_CON1_EN (0x1 << 0)
@ -449,6 +450,7 @@ static int mtk_afe_hdmi_prepare(struct snd_pcm_substream *substream,
runtime->rate * runtime->channels * 32);
val = AFE_TDM_CON1_BCK_INV |
AFE_TDM_CON1_LRCK_INV |
AFE_TDM_CON1_1_BCK_DELAY |
AFE_TDM_CON1_MSB_ALIGNED | /* I2S mode */
AFE_TDM_CON1_WLEN_32BIT |

View file

@ -260,6 +260,10 @@ static void omap_st_on(struct omap_mcbsp *mcbsp)
if (mcbsp->pdata->enable_st_clock)
mcbsp->pdata->enable_st_clock(mcbsp->id, 1);
/* Disable Sidetone clock auto-gating for normal operation */
w = MCBSP_ST_READ(mcbsp, SYSCONFIG);
MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w & ~(ST_AUTOIDLE));
/* Enable McBSP Sidetone */
w = MCBSP_READ(mcbsp, SSELCR);
MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN);
@ -279,6 +283,10 @@ static void omap_st_off(struct omap_mcbsp *mcbsp)
w = MCBSP_READ(mcbsp, SSELCR);
MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN));
/* Enable Sidetone clock auto-gating to reduce power consumption */
w = MCBSP_ST_READ(mcbsp, SYSCONFIG);
MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w | ST_AUTOIDLE);
if (mcbsp->pdata->enable_st_clock)
mcbsp->pdata->enable_st_clock(mcbsp->id, 0);
}

View file

@ -82,6 +82,8 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream,
struct dma_chan *chan;
int err = 0;
memset(&config, 0x00, sizeof(config));
dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
/* return if this is a bufferless transfer e.g.

View file

@ -133,3 +133,4 @@ module_platform_driver(mmp_driver);
MODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
MODULE_DESCRIPTION("ALSA SoC Brownstone");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:brownstone-audio");

View file

@ -207,3 +207,4 @@ module_platform_driver(mioa701_wm9713_driver);
MODULE_AUTHOR("Robert Jarzmik (rjarzmik@free.fr)");
MODULE_DESCRIPTION("ALSA SoC WM9713 MIO A701");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mioa701-wm9713");

View file

@ -248,3 +248,4 @@ module_platform_driver(mmp_pcm_driver);
MODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
MODULE_DESCRIPTION("MMP Soc Audio DMA module");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mmp-pcm-audio");

View file

@ -482,3 +482,4 @@ module_platform_driver(asoc_mmp_sspa_driver);
MODULE_AUTHOR("Leo Yan <leoy@marvell.com>");
MODULE_DESCRIPTION("MMP SSPA SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mmp-sspa-dai");

View file

@ -161,3 +161,4 @@ module_platform_driver(palm27x_wm9712_driver);
MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
MODULE_DESCRIPTION("ALSA SoC Palm T|X, T5 and LifeDrive");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:palm27x-asoc");

View file

@ -833,3 +833,4 @@ module_platform_driver(asoc_ssp_driver);
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_DESCRIPTION("PXA SSP/PCM SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pxa-ssp-dai");

View file

@ -287,3 +287,4 @@ module_platform_driver(pxa2xx_ac97_driver);
MODULE_AUTHOR("Nicolas Pitre");
MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pxa2xx-ac97");

View file

@ -117,3 +117,4 @@ module_platform_driver(pxa_pcm_driver);
MODULE_AUTHOR("Nicolas Pitre");
MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pxa-pcm-audio");

View file

@ -474,7 +474,7 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime)
struct lpass_data *drvdata =
snd_soc_platform_get_drvdata(soc_runtime->platform);
struct lpass_variant *v = drvdata->variant;
int ret;
int ret = -EINVAL;
struct lpass_pcm_data *data;
size_t size = lpass_platform_pcm_hardware.buffer_bytes_max;
@ -518,8 +518,10 @@ static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime)
data->wrdma_ch = v->alloc_dma_channel(drvdata,
SNDRV_PCM_STREAM_CAPTURE);
if (data->wrdma_ch < 0)
if (data->wrdma_ch < 0) {
ret = data->wrdma_ch;
goto capture_alloc_err;
}
drvdata->substream[data->wrdma_ch] = csubstream;

View file

@ -492,9 +492,7 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv,
*/
if (!count) {
clk = clk_register_fixed_rate(dev, clkout_name[CLKOUT],
parent_clk_name,
(parent_clk_name) ?
0 : CLK_IS_ROOT, req_rate);
parent_clk_name, 0, req_rate);
if (!IS_ERR(clk)) {
adg->clkout[CLKOUT] = clk;
of_clk_add_provider(np, of_clk_src_simple_get, clk);
@ -506,9 +504,7 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv,
else {
for (i = 0; i < CLKOUTMAX; i++) {
clk = clk_register_fixed_rate(dev, clkout_name[i],
parent_clk_name,
(parent_clk_name) ?
0 : CLK_IS_ROOT,
parent_clk_name, 0,
req_rate);
if (!IS_ERR(clk)) {
adg->onecell.clks = adg->clkout;

View file

@ -316,11 +316,15 @@ static u32 rsnd_dmapp_get_id(struct rsnd_dai_stream *io,
size = ARRAY_SIZE(gen2_id_table_cmd);
}
if (!entry)
return 0xFF;
if ((!entry) || (size <= id)) {
struct device *dev = rsnd_priv_to_dev(rsnd_io_to_priv(io));
if (size <= id)
return 0xFF;
dev_err(dev, "unknown connection (%s[%d])\n",
rsnd_mod_name(mod), rsnd_mod_id(mod));
/* use non-prohibited SRS number as error */
return 0x00; /* SSI00 */
}
return entry[id];
}

View file

@ -276,8 +276,9 @@ struct rsnd_mod {
/*
* status
*
* 0xH0000CB0
* 0xH0000CBA
*
* A 0: probe 1: remove
* B 0: init 1: quit
* C 0: start 1: stop
*
@ -287,19 +288,19 @@ struct rsnd_mod {
* H 0: fallback
* H 0: hw_params
*/
#define __rsnd_mod_shift_probe 0
#define __rsnd_mod_shift_remove 0
#define __rsnd_mod_shift_init 4
#define __rsnd_mod_shift_quit 4
#define __rsnd_mod_shift_start 8
#define __rsnd_mod_shift_stop 8
#define __rsnd_mod_shift_probe 28 /* always called */
#define __rsnd_mod_shift_remove 28 /* always called */
#define __rsnd_mod_shift_irq 28 /* always called */
#define __rsnd_mod_shift_pcm_new 28 /* always called */
#define __rsnd_mod_shift_fallback 28 /* always called */
#define __rsnd_mod_shift_hw_params 28 /* always called */
#define __rsnd_mod_add_probe 0
#define __rsnd_mod_add_remove 0
#define __rsnd_mod_add_probe 1
#define __rsnd_mod_add_remove -1
#define __rsnd_mod_add_init 1
#define __rsnd_mod_add_quit -1
#define __rsnd_mod_add_start 1
@ -310,7 +311,7 @@ struct rsnd_mod {
#define __rsnd_mod_add_hw_params 0
#define __rsnd_mod_call_probe 0
#define __rsnd_mod_call_remove 0
#define __rsnd_mod_call_remove 1
#define __rsnd_mod_call_init 0
#define __rsnd_mod_call_quit 1
#define __rsnd_mod_call_start 0

View file

@ -572,6 +572,9 @@ int rsnd_src_probe(struct rsnd_priv *priv)
i = 0;
for_each_child_of_node(node, np) {
if (!of_device_is_available(np))
goto skip;
src = rsnd_src_get(priv, i);
snprintf(name, RSND_SRC_NAME_SIZE, "%s.%d",
@ -595,6 +598,7 @@ int rsnd_src_probe(struct rsnd_priv *priv)
if (ret)
goto rsnd_src_probe_done;
skip:
i++;
}

View file

@ -1023,6 +1023,11 @@ static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg,
control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos;
if (control_hdr->size != sizeof(*control_hdr)) {
dev_err(tplg->dev, "ASoC: invalid control size\n");
return -EINVAL;
}
switch (control_hdr->ops.info) {
case SND_SOC_TPLG_CTL_VOLSW:
case SND_SOC_TPLG_CTL_STROBE:
@ -1476,6 +1481,8 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg,
widget->dobj.type = SND_SOC_DOBJ_WIDGET;
widget->dobj.ops = tplg->ops;
widget->dobj.index = tplg->index;
kfree(template.sname);
kfree(template.name);
list_add(&widget->dobj.list, &tplg->comp->dobj_list);
return 0;
@ -1499,10 +1506,17 @@ static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg,
for (i = 0; i < count; i++) {
widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos;
if (widget->size != sizeof(*widget)) {
dev_err(tplg->dev, "ASoC: invalid widget size\n");
return -EINVAL;
}
ret = soc_tplg_dapm_widget_create(tplg, widget);
if (ret < 0)
if (ret < 0) {
dev_err(tplg->dev, "ASoC: failed to load widget %s\n",
widget->name);
return ret;
}
}
return 0;
@ -1586,6 +1600,7 @@ static int soc_tplg_dai_create(struct soc_tplg *tplg,
return snd_soc_register_dai(tplg->comp, dai_drv);
}
/* create the FE DAI link */
static int soc_tplg_link_create(struct soc_tplg *tplg,
struct snd_soc_tplg_pcm *pcm)
{
@ -1598,6 +1613,16 @@ static int soc_tplg_link_create(struct soc_tplg *tplg,
link->name = pcm->pcm_name;
link->stream_name = pcm->pcm_name;
link->id = pcm->pcm_id;
link->cpu_dai_name = pcm->dai_name;
link->codec_name = "snd-soc-dummy";
link->codec_dai_name = "snd-soc-dummy-dai";
/* enable DPCM */
link->dynamic = 1;
link->dpcm_playback = pcm->playback;
link->dpcm_capture = pcm->capture;
/* pass control to component driver for optional further init */
ret = soc_tplg_dai_link_load(tplg, link);
@ -1639,8 +1664,6 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg,
if (tplg->pass != SOC_TPLG_PASS_PCM_DAI)
return 0;
pcm = (struct snd_soc_tplg_pcm *)tplg->pos;
if (soc_tplg_check_elem_count(tplg,
sizeof(struct snd_soc_tplg_pcm), count,
hdr->payload_size, "PCM DAI")) {
@ -1650,7 +1673,13 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg,
}
/* create the FE DAIs and DAI links */
pcm = (struct snd_soc_tplg_pcm *)tplg->pos;
for (i = 0; i < count; i++) {
if (pcm->size != sizeof(*pcm)) {
dev_err(tplg->dev, "ASoC: invalid pcm size\n");
return -EINVAL;
}
soc_tplg_pcm_create(tplg, pcm);
pcm++;
}
@ -1670,6 +1699,11 @@ static int soc_tplg_manifest_load(struct soc_tplg *tplg,
return 0;
manifest = (struct snd_soc_tplg_manifest *)tplg->pos;
if (manifest->size != sizeof(*manifest)) {
dev_err(tplg->dev, "ASoC: invalid manifest size\n");
return -EINVAL;
}
tplg->pos += sizeof(struct snd_soc_tplg_manifest);
if (tplg->comp && tplg->ops && tplg->ops->manifest)
@ -1686,6 +1720,14 @@ static int soc_valid_header(struct soc_tplg *tplg,
if (soc_tplg_get_hdr_offset(tplg) >= tplg->fw->size)
return 0;
if (hdr->size != sizeof(*hdr)) {
dev_err(tplg->dev,
"ASoC: invalid header size for type %d at offset 0x%lx size 0x%zx.\n",
hdr->type, soc_tplg_get_hdr_offset(tplg),
tplg->fw->size);
return -EINVAL;
}
/* big endian firmware objects not supported atm */
if (hdr->magic == cpu_to_be32(SND_SOC_TPLG_MAGIC)) {
dev_err(tplg->dev,

View file

@ -10,6 +10,142 @@
#include "uniperif.h"
/*
* User frame size shall be 2, 4, 6 or 8 32-bits words length
* (i.e. 8, 16, 24 or 32 bytes)
* This constraint comes from allowed values for
* UNIPERIF_I2S_FMT_NUM_CH register
*/
#define UNIPERIF_MAX_FRAME_SZ 0x20
#define UNIPERIF_ALLOWED_FRAME_SZ (0x08 | 0x10 | 0x18 | UNIPERIF_MAX_FRAME_SZ)
int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots,
int slot_width)
{
struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
struct uniperif *uni = priv->dai_data.uni;
int i, frame_size, avail_slots;
if (!UNIPERIF_TYPE_IS_TDM(uni)) {
dev_err(uni->dev, "cpu dai not in tdm mode\n");
return -EINVAL;
}
/* store info in unip context */
uni->tdm_slot.slots = slots;
uni->tdm_slot.slot_width = slot_width;
/* unip is unidirectionnal */
uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask;
/* number of available timeslots */
for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) {
if ((uni->tdm_slot.mask >> i) & 0x01)
avail_slots++;
}
uni->tdm_slot.avail_slots = avail_slots;
/* frame size in bytes */
frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8;
/* check frame size is allowed */
if ((frame_size > UNIPERIF_MAX_FRAME_SZ) ||
(frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) {
dev_err(uni->dev, "frame size not allowed: %d bytes\n",
frame_size);
return -EINVAL;
}
return 0;
}
int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct uniperif *uni = rule->private;
struct snd_interval t;
t.min = uni->tdm_slot.avail_slots;
t.max = uni->tdm_slot.avail_slots;
t.openmin = 0;
t.openmax = 0;
t.integer = 0;
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
}
int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct uniperif *uni = rule->private;
struct snd_mask *maskp = hw_param_mask(params, rule->var);
u64 format;
switch (uni->tdm_slot.slot_width) {
case 16:
format = SNDRV_PCM_FMTBIT_S16_LE;
break;
case 32:
format = SNDRV_PCM_FMTBIT_S32_LE;
break;
default:
dev_err(uni->dev, "format not supported: %d bits\n",
uni->tdm_slot.slot_width);
return -EINVAL;
}
maskp->bits[0] &= (u_int32_t)format;
maskp->bits[1] &= (u_int32_t)(format >> 32);
/* clear remaining indexes */
memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8);
if (!maskp->bits[0] && !maskp->bits[1])
return -EINVAL;
return 0;
}
int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni,
unsigned int *word_pos)
{
int slot_width = uni->tdm_slot.slot_width / 8;
int slots_num = uni->tdm_slot.slots;
unsigned int slots_mask = uni->tdm_slot.mask;
int i, j, k;
unsigned int word16_pos[4];
/* word16_pos:
* word16_pos[0] = WORDX_LSB
* word16_pos[1] = WORDX_MSB,
* word16_pos[2] = WORDX+1_LSB
* word16_pos[3] = WORDX+1_MSB
*/
/* set unip word position */
for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) {
if ((slots_mask >> i) & 0x01) {
word16_pos[j] = i * slot_width;
if (slot_width == 4) {
word16_pos[j + 1] = word16_pos[j] + 2;
j++;
}
j++;
if (j > 3) {
word_pos[k] = word16_pos[1] |
(word16_pos[0] << 8) |
(word16_pos[3] << 16) |
(word16_pos[2] << 24);
j = 0;
k++;
}
}
}
return 0;
}
/*
* sti_uniperiph_dai_create_ctrl
* This function is used to create Ctrl associated to DAI but also pcm device.
@ -45,10 +181,16 @@ int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
struct uniperif *uni = priv->dai_data.uni;
struct snd_dmaengine_dai_dma_data *dma_data;
int transfer_size;
transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES;
if (uni->info->type == SND_ST_UNIPERIF_TYPE_TDM)
/* transfer size = user frame size (in 32-bits FIFO cell) */
transfer_size = snd_soc_params_to_frame_size(params) / 32;
else
transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES;
dma_data = snd_soc_dai_get_dma_data(dai, substream);
dma_data->maxburst = transfer_size;

View file

@ -25,7 +25,7 @@
writel_relaxed((((value) & mask) << shift), ip->base + offset)
/*
* AUD_UNIPERIF_SOFT_RST reg
* UNIPERIF_SOFT_RST reg
*/
#define UNIPERIF_SOFT_RST_OFFSET(ip) 0x0000
@ -50,7 +50,7 @@
UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip))
/*
* AUD_UNIPERIF_FIFO_DATA reg
* UNIPERIF_FIFO_DATA reg
*/
#define UNIPERIF_FIFO_DATA_OFFSET(ip) 0x0004
@ -58,7 +58,7 @@
writel_relaxed(value, ip->base + UNIPERIF_FIFO_DATA_OFFSET(ip))
/*
* AUD_UNIPERIF_CHANNEL_STA_REGN reg
* UNIPERIF_CHANNEL_STA_REGN reg
*/
#define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n))
@ -105,7 +105,7 @@
writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip))
/*
* AUD_UNIPERIF_ITS reg
* UNIPERIF_ITS reg
*/
#define UNIPERIF_ITS_OFFSET(ip) 0x000C
@ -143,7 +143,7 @@
0 : (BIT(UNIPERIF_ITS_UNDERFLOW_REC_FAILED_SHIFT(ip))))
/*
* AUD_UNIPERIF_ITS_BCLR reg
* UNIPERIF_ITS_BCLR reg
*/
/* FIFO_ERROR */
@ -160,7 +160,7 @@
writel_relaxed(value, ip->base + UNIPERIF_ITS_BCLR_OFFSET(ip))
/*
* AUD_UNIPERIF_ITM reg
* UNIPERIF_ITM reg
*/
#define UNIPERIF_ITM_OFFSET(ip) 0x0018
@ -188,7 +188,7 @@
0 : (BIT(UNIPERIF_ITM_UNDERFLOW_REC_FAILED_SHIFT(ip))))
/*
* AUD_UNIPERIF_ITM_BCLR reg
* UNIPERIF_ITM_BCLR reg
*/
#define UNIPERIF_ITM_BCLR_OFFSET(ip) 0x001c
@ -213,7 +213,7 @@
UNIPERIF_ITM_BCLR_DMA_ERROR_MASK(ip))
/*
* AUD_UNIPERIF_ITM_BSET reg
* UNIPERIF_ITM_BSET reg
*/
#define UNIPERIF_ITM_BSET_OFFSET(ip) 0x0020
@ -767,7 +767,7 @@
SET_UNIPERIF_REG(ip, \
UNIPERIF_CTRL_OFFSET(ip), \
UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \
CORAUD_UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 1)
UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 1)
/* UNDERFLOW_REC_WINDOW */
#define UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip) 20
@ -1046,7 +1046,7 @@
UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip), value)
/*
* AUD_UNIPERIF_CHANNEL_STA_REGN reg
* UNIPERIF_CHANNEL_STA_REGN reg
*/
#define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n))
@ -1057,7 +1057,7 @@
UNIPERIF_CHANNEL_STA_REGN(ip, n))
/*
* AUD_UNIPERIF_USER_VALIDITY reg
* UNIPERIF_USER_VALIDITY reg
*/
#define UNIPERIF_USER_VALIDITY_OFFSET(ip) 0x0090
@ -1100,6 +1100,118 @@
UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip), \
UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip), value)
/*
* UNIPERIF_TDM_ENABLE
*/
#define UNIPERIF_TDM_ENABLE_OFFSET(ip) 0x0118
#define GET_UNIPERIF_TDM_ENABLE(ip) \
readl_relaxed(ip->base + UNIPERIF_TDM_ENABLE_OFFSET(ip))
#define SET_UNIPERIF_TDM_ENABLE(ip, value) \
writel_relaxed(value, ip->base + UNIPERIF_TDM_ENABLE_OFFSET(ip))
/* TDM_ENABLE */
#define UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip) 0x0
#define UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip) 0x1
#define GET_UNIPERIF_TDM_ENABLE_EN_TDM(ip) \
GET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_ENABLE_OFFSET(ip), \
UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \
UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip))
#define SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(ip) \
SET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_ENABLE_OFFSET(ip), \
UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \
UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip), 1)
#define SET_UNIPERIF_TDM_ENABLE_TDM_DISABLE(ip) \
SET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_ENABLE_OFFSET(ip), \
UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \
UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip), 0)
/*
* UNIPERIF_TDM_FS_REF_FREQ
*/
#define UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip) 0x011c
#define GET_UNIPERIF_TDM_FS_REF_FREQ(ip) \
readl_relaxed(ip->base + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip))
#define SET_UNIPERIF_TDM_FS_REF_FREQ(ip, value) \
writel_relaxed(value, ip->base + \
UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip))
/* REF_FREQ */
#define UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip) 0x0
#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip) 0
#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip) 1
#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip) 2
#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip) 3
#define UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip) 0x3
#define GET_UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ(ip) \
GET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip))
#define SET_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip) \
SET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \
VALUE_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip))
#define SET_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip) \
SET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \
VALUE_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip))
#define SET_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip) \
SET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \
VALUE_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip))
#define SET_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip) \
SET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \
UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \
VALUE_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip))
/*
* UNIPERIF_TDM_FS_REF_DIV
*/
#define UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip) 0x0120
#define GET_UNIPERIF_TDM_FS_REF_DIV(ip) \
readl_relaxed(ip->base + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip))
#define SET_UNIPERIF_TDM_FS_REF_DIV(ip, value) \
writel_relaxed(value, ip->base + \
UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip))
/* NUM_TIMESLOT */
#define UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip) 0x0
#define UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip) 0xff
#define GET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(ip) \
GET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip), \
UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip), \
UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip))
#define SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(ip, value) \
SET_UNIPERIF_REG(ip, \
UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip), \
UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip), \
UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip), value)
/*
* UNIPERIF_TDM_WORD_POS_X_Y
* 32 bits of UNIPERIF_TDM_WORD_POS_X_Y register shall be set in 1 shot
*/
#define UNIPERIF_TDM_WORD_POS_1_2_OFFSET(ip) 0x013c
#define UNIPERIF_TDM_WORD_POS_3_4_OFFSET(ip) 0x0140
#define UNIPERIF_TDM_WORD_POS_5_6_OFFSET(ip) 0x0144
#define UNIPERIF_TDM_WORD_POS_7_8_OFFSET(ip) 0x0148
#define GET_UNIPERIF_TDM_WORD_POS(ip, words) \
readl_relaxed(ip->base + UNIPERIF_TDM_WORD_POS_##words##_OFFSET(ip))
#define SET_UNIPERIF_TDM_WORD_POS(ip, words, value) \
writel_relaxed(value, ip->base + \
UNIPERIF_TDM_WORD_POS_##words##_OFFSET(ip))
/*
* uniperipheral IP capabilities
*/
@ -1107,6 +1219,18 @@
#define UNIPERIF_FIFO_SIZE 70 /* FIFO is 70 cells deep */
#define UNIPERIF_FIFO_FRAMES 4 /* FDMA trigger limit in frames */
#define UNIPERIF_TYPE_IS_HDMI(p) \
((p)->info->type == SND_ST_UNIPERIF_TYPE_HDMI)
#define UNIPERIF_TYPE_IS_PCM(p) \
((p)->info->type == SND_ST_UNIPERIF_TYPE_PCM)
#define UNIPERIF_TYPE_IS_SPDIF(p) \
((p)->info->type == SND_ST_UNIPERIF_TYPE_SPDIF)
#define UNIPERIF_TYPE_IS_IEC958(p) \
(UNIPERIF_TYPE_IS_HDMI(p) || \
UNIPERIF_TYPE_IS_SPDIF(p))
#define UNIPERIF_TYPE_IS_TDM(p) \
((p)->info->type == SND_ST_UNIPERIF_TYPE_TDM)
/*
* Uniperipheral IP revisions
*/
@ -1125,10 +1249,11 @@ enum uniperif_version {
};
enum uniperif_type {
SND_ST_UNIPERIF_PLAYER_TYPE_NONE,
SND_ST_UNIPERIF_PLAYER_TYPE_HDMI,
SND_ST_UNIPERIF_PLAYER_TYPE_PCM,
SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF
SND_ST_UNIPERIF_TYPE_NONE,
SND_ST_UNIPERIF_TYPE_HDMI,
SND_ST_UNIPERIF_TYPE_PCM,
SND_ST_UNIPERIF_TYPE_SPDIF,
SND_ST_UNIPERIF_TYPE_TDM
};
enum uniperif_state {
@ -1145,9 +1270,17 @@ enum uniperif_iec958_encoding_mode {
UNIPERIF_IEC958_ENCODING_MODE_ENCODED
};
enum uniperif_word_pos {
WORD_1_2,
WORD_3_4,
WORD_5_6,
WORD_7_8,
WORD_MAX
};
struct uniperif_info {
int id; /* instance value of the uniperipheral IP */
enum uniperif_type player_type;
enum uniperif_type type;
int underflow_enabled; /* Underflow recovery mode */
};
@ -1156,12 +1289,20 @@ struct uniperif_iec958_settings {
struct snd_aes_iec958 iec958;
};
struct dai_tdm_slot {
unsigned int mask;
int slots;
int slot_width;
unsigned int avail_slots;
};
struct uniperif {
/* System information */
struct uniperif_info *info;
struct device *dev;
int ver; /* IP version, used by register access macros */
struct regmap_field *clk_sel;
struct regmap_field *valid_sel;
/* capabilities */
const struct snd_pcm_hardware *hw;
@ -1192,6 +1333,7 @@ struct uniperif {
/* dai properties */
unsigned int daifmt;
struct dai_tdm_slot tdm_slot;
/* DAI callbacks */
const struct snd_soc_dai_ops *dai_ops;
@ -1209,6 +1351,28 @@ struct sti_uniperiph_data {
struct sti_uniperiph_dai dai_data;
};
static const struct snd_pcm_hardware uni_tdm_hw = {
.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 32,
.periods_min = 2,
.periods_max = 10,
.period_bytes_min = 128,
.period_bytes_max = 64 * PAGE_SIZE,
.buffer_bytes_max = 256 * PAGE_SIZE
};
/* uniperiph player*/
int uni_player_init(struct platform_device *pdev,
struct uniperif *uni_player);
@ -1226,4 +1390,28 @@ int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai);
static inline int sti_uniperiph_get_user_frame_size(
struct snd_pcm_runtime *runtime)
{
return (runtime->channels * snd_pcm_format_width(runtime->format) / 8);
}
static inline int sti_uniperiph_get_unip_tdm_frame_size(struct uniperif *uni)
{
return (uni->tdm_slot.slots * uni->tdm_slot.slot_width / 8);
}
int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots,
int slot_width);
int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni,
unsigned int *word_pos);
int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule);
int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule);
#endif

View file

@ -21,23 +21,14 @@
/* sys config registers definitions */
#define SYS_CFG_AUDIO_GLUE 0xA4
#define SYS_CFG_AUDI0_GLUE_PCM_CLKX 8
/*
* Driver specific types.
*/
#define UNIPERIF_PLAYER_TYPE_IS_HDMI(p) \
((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_HDMI)
#define UNIPERIF_PLAYER_TYPE_IS_PCM(p) \
((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_PCM)
#define UNIPERIF_PLAYER_TYPE_IS_SPDIF(p) \
((p)->info->player_type == SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF)
#define UNIPERIF_PLAYER_TYPE_IS_IEC958(p) \
(UNIPERIF_PLAYER_TYPE_IS_HDMI(p) || \
UNIPERIF_PLAYER_TYPE_IS_SPDIF(p))
#define UNIPERIF_PLAYER_CLK_ADJ_MIN -999999
#define UNIPERIF_PLAYER_CLK_ADJ_MAX 1000000
#define UNIPERIF_PLAYER_I2S_OUT 1 /* player id connected to I2S/TDM TX bus */
/*
* Note: snd_pcm_hardware is linked to DMA controller but is declared here to
@ -444,18 +435,11 @@ static int uni_player_prepare_pcm(struct uniperif *player,
/* Force slot width to 32 in I2S mode (HW constraint) */
if ((player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) ==
SND_SOC_DAIFMT_I2S) {
SND_SOC_DAIFMT_I2S)
slot_width = 32;
} else {
switch (runtime->format) {
case SNDRV_PCM_FORMAT_S16_LE:
slot_width = 16;
break;
default:
slot_width = 32;
break;
}
}
else
slot_width = snd_pcm_format_width(runtime->format);
output_frame_size = slot_width * runtime->channels;
clk_div = player->mclk / runtime->rate;
@ -530,7 +514,6 @@ static int uni_player_prepare_pcm(struct uniperif *player,
SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player);
SET_UNIPERIF_I2S_FMT_ORDER_MSB(player);
SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(player);
/* No iec958 formatting as outputting to DAC */
SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player);
@ -538,6 +521,55 @@ static int uni_player_prepare_pcm(struct uniperif *player,
return 0;
}
static int uni_player_prepare_tdm(struct uniperif *player,
struct snd_pcm_runtime *runtime)
{
int tdm_frame_size; /* unip tdm frame size in bytes */
int user_frame_size; /* user tdm frame size in bytes */
/* default unip TDM_WORD_POS_X_Y */
unsigned int word_pos[4] = {
0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A};
int freq, ret;
tdm_frame_size =
sti_uniperiph_get_unip_tdm_frame_size(player);
user_frame_size =
sti_uniperiph_get_user_frame_size(runtime);
/* fix 16/0 format */
SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player);
SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(player);
/* number of words inserted on the TDM line */
SET_UNIPERIF_I2S_FMT_NUM_CH(player, user_frame_size / 4 / 2);
SET_UNIPERIF_I2S_FMT_ORDER_MSB(player);
SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player);
/* Enable the tdm functionality */
SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(player);
/* number of 8 bits timeslots avail in unip tdm frame */
SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(player, tdm_frame_size);
/* set the timeslot allocation for words in FIFO */
sti_uniperiph_get_tdm_word_pos(player, word_pos);
SET_UNIPERIF_TDM_WORD_POS(player, 1_2, word_pos[WORD_1_2]);
SET_UNIPERIF_TDM_WORD_POS(player, 3_4, word_pos[WORD_3_4]);
SET_UNIPERIF_TDM_WORD_POS(player, 5_6, word_pos[WORD_5_6]);
SET_UNIPERIF_TDM_WORD_POS(player, 7_8, word_pos[WORD_7_8]);
/* set unip clk rate (not done vai set_sysclk ops) */
freq = runtime->rate * tdm_frame_size * 8;
mutex_lock(&player->ctrl_lock);
ret = uni_player_clk_set_rate(player, freq);
if (!ret)
player->mclk = freq;
mutex_unlock(&player->ctrl_lock);
return 0;
}
/*
* ALSA uniperipheral iec958 controls
*/
@ -668,11 +700,29 @@ static int uni_player_startup(struct snd_pcm_substream *substream,
{
struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
struct uniperif *player = priv->dai_data.uni;
int ret;
player->substream = substream;
player->clk_adj = 0;
return 0;
if (!UNIPERIF_TYPE_IS_TDM(player))
return 0;
/* refine hw constraint in tdm mode */
ret = snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
sti_uniperiph_fix_tdm_chan,
player, SNDRV_PCM_HW_PARAM_CHANNELS,
-1);
if (ret < 0)
return ret;
return snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_FORMAT,
sti_uniperiph_fix_tdm_format,
player, SNDRV_PCM_HW_PARAM_FORMAT,
-1);
}
static int uni_player_set_sysclk(struct snd_soc_dai *dai, int clk_id,
@ -682,7 +732,7 @@ static int uni_player_set_sysclk(struct snd_soc_dai *dai, int clk_id,
struct uniperif *player = priv->dai_data.uni;
int ret;
if (dir == SND_SOC_CLOCK_IN)
if (UNIPERIF_TYPE_IS_TDM(player) || (dir == SND_SOC_CLOCK_IN))
return 0;
if (clk_id != 0)
@ -714,7 +764,13 @@ static int uni_player_prepare(struct snd_pcm_substream *substream,
}
/* Calculate transfer size (in fifo cells and bytes) for frame count */
transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES;
if (player->info->type == SND_ST_UNIPERIF_TYPE_TDM) {
/* transfer size = user frame size (in 32 bits FIFO cell) */
transfer_size =
sti_uniperiph_get_user_frame_size(runtime) / 4;
} else {
transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES;
}
/* Calculate number of empty cells available before asserting DREQ */
if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) {
@ -738,16 +794,19 @@ static int uni_player_prepare(struct snd_pcm_substream *substream,
SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(player, trigger_limit);
/* Uniperipheral setup depends on player type */
switch (player->info->player_type) {
case SND_ST_UNIPERIF_PLAYER_TYPE_HDMI:
switch (player->info->type) {
case SND_ST_UNIPERIF_TYPE_HDMI:
ret = uni_player_prepare_iec958(player, runtime);
break;
case SND_ST_UNIPERIF_PLAYER_TYPE_PCM:
case SND_ST_UNIPERIF_TYPE_PCM:
ret = uni_player_prepare_pcm(player, runtime);
break;
case SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF:
case SND_ST_UNIPERIF_TYPE_SPDIF:
ret = uni_player_prepare_iec958(player, runtime);
break;
case SND_ST_UNIPERIF_TYPE_TDM:
ret = uni_player_prepare_tdm(player, runtime);
break;
default:
dev_err(player->dev, "invalid player type");
return -EINVAL;
@ -852,8 +911,8 @@ static int uni_player_start(struct uniperif *player)
* will not take affect and hang the player.
*/
if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
if (UNIPERIF_PLAYER_TYPE_IS_IEC958(player))
SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player);
if (UNIPERIF_TYPE_IS_IEC958(player))
SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player);
/* Force channel status update (no update if clk disable) */
if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
@ -954,27 +1013,30 @@ static void uni_player_shutdown(struct snd_pcm_substream *substream,
player->substream = NULL;
}
static int uni_player_parse_dt_clk_glue(struct platform_device *pdev,
struct uniperif *player)
static int uni_player_parse_dt_audio_glue(struct platform_device *pdev,
struct uniperif *player)
{
int bit_offset;
struct device_node *node = pdev->dev.of_node;
struct regmap *regmap;
bit_offset = SYS_CFG_AUDI0_GLUE_PCM_CLKX + player->info->id;
struct reg_field regfield[2] = {
/* PCM_CLK_SEL */
REG_FIELD(SYS_CFG_AUDIO_GLUE,
8 + player->info->id,
8 + player->info->id),
/* PCMP_VALID_SEL */
REG_FIELD(SYS_CFG_AUDIO_GLUE, 0, 1)
};
regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg");
if (regmap) {
struct reg_field regfield =
REG_FIELD(SYS_CFG_AUDIO_GLUE, bit_offset, bit_offset);
player->clk_sel = regmap_field_alloc(regmap, regfield);
} else {
if (!regmap) {
dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n");
return -EINVAL;
}
player->clk_sel = regmap_field_alloc(regmap, regfield[0]);
player->valid_sel = regmap_field_alloc(regmap, regfield[1]);
return 0;
}
@ -1012,19 +1074,21 @@ static int uni_player_parse_dt(struct platform_device *pdev,
}
if (strcasecmp(mode, "hdmi") == 0)
info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_HDMI;
info->type = SND_ST_UNIPERIF_TYPE_HDMI;
else if (strcasecmp(mode, "pcm") == 0)
info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_PCM;
info->type = SND_ST_UNIPERIF_TYPE_PCM;
else if (strcasecmp(mode, "spdif") == 0)
info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_SPDIF;
info->type = SND_ST_UNIPERIF_TYPE_SPDIF;
else if (strcasecmp(mode, "tdm") == 0)
info->type = SND_ST_UNIPERIF_TYPE_TDM;
else
info->player_type = SND_ST_UNIPERIF_PLAYER_TYPE_NONE;
info->type = SND_ST_UNIPERIF_TYPE_NONE;
/* Save the info structure */
player->info = info;
/* Get the PCM_CLK_SEL bit from audio-glue-ctrl SoC register */
if (uni_player_parse_dt_clk_glue(pdev, player))
/* Get PCM_CLK_SEL & PCMP_VALID_SEL from audio-glue-ctrl SoC reg */
if (uni_player_parse_dt_audio_glue(pdev, player))
return -EINVAL;
return 0;
@ -1037,7 +1101,8 @@ static const struct snd_soc_dai_ops uni_player_dai_ops = {
.trigger = uni_player_trigger,
.hw_params = sti_uniperiph_dai_hw_params,
.set_fmt = sti_uniperiph_dai_set_fmt,
.set_sysclk = uni_player_set_sysclk
.set_sysclk = uni_player_set_sysclk,
.set_tdm_slot = sti_uniperiph_set_tdm_slot
};
int uni_player_init(struct platform_device *pdev,
@ -1047,7 +1112,6 @@ int uni_player_init(struct platform_device *pdev,
player->dev = &pdev->dev;
player->state = UNIPERIF_STATE_STOPPED;
player->hw = &uni_player_pcm_hw;
player->dai_ops = &uni_player_dai_ops;
ret = uni_player_parse_dt(pdev, player);
@ -1057,6 +1121,11 @@ int uni_player_init(struct platform_device *pdev,
return ret;
}
if (UNIPERIF_TYPE_IS_TDM(player))
player->hw = &uni_tdm_hw;
else
player->hw = &uni_player_pcm_hw;
/* Get uniperif resource */
player->clk = of_clk_get(pdev->dev.of_node, 0);
if (IS_ERR(player->clk))
@ -1073,6 +1142,17 @@ int uni_player_init(struct platform_device *pdev,
}
}
/* connect to I2S/TDM TX bus */
if (player->valid_sel &&
(player->info->id == UNIPERIF_PLAYER_I2S_OUT)) {
ret = regmap_field_write(player->valid_sel, player->info->id);
if (ret) {
dev_err(player->dev,
"%s: unable to connect to tdm bus", __func__);
return ret;
}
}
ret = devm_request_irq(&pdev->dev, player->irq,
uni_player_irq_handler, IRQF_SHARED,
dev_name(&pdev->dev), player);
@ -1087,7 +1167,7 @@ int uni_player_init(struct platform_device *pdev,
SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player);
SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player);
if (UNIPERIF_PLAYER_TYPE_IS_IEC958(player)) {
if (UNIPERIF_TYPE_IS_IEC958(player)) {
/* Set default iec958 status bits */
/* Consumer, PCM, copyright, 2ch, mode 0 */

View file

@ -73,55 +73,10 @@ static irqreturn_t uni_reader_irq_handler(int irq, void *dev_id)
return ret;
}
static int uni_reader_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
static int uni_reader_prepare_pcm(struct snd_pcm_runtime *runtime,
struct uniperif *reader)
{
struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
struct uniperif *reader = priv->dai_data.uni;
struct snd_pcm_runtime *runtime = substream->runtime;
int transfer_size, trigger_limit;
int slot_width;
int count = 10;
/* The reader should be stopped */
if (reader->state != UNIPERIF_STATE_STOPPED) {
dev_err(reader->dev, "%s: invalid reader state %d", __func__,
reader->state);
return -EINVAL;
}
/* Calculate transfer size (in fifo cells and bytes) for frame count */
transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES;
/* Calculate number of empty cells available before asserting DREQ */
if (reader->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size;
else
/*
* Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0
* FDMA_TRIGGER_LIMIT also controls when the state switches
* from OFF or STANDBY to AUDIO DATA.
*/
trigger_limit = transfer_size;
/* Trigger limit must be an even number */
if ((!trigger_limit % 2) ||
(trigger_limit != 1 && transfer_size % 2) ||
(trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(reader))) {
dev_err(reader->dev, "invalid trigger limit %d", trigger_limit);
return -EINVAL;
}
SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(reader, trigger_limit);
switch (reader->daifmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_IB_IF:
case SND_SOC_DAIFMT_NB_IF:
SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader);
break;
default:
SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader);
}
/* Force slot width to 32 in I2S mode */
if ((reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK)
@ -173,6 +128,109 @@ static int uni_reader_prepare(struct snd_pcm_substream *substream,
return -EINVAL;
}
/* Number of channels must be even */
if ((runtime->channels % 2) || (runtime->channels < 2) ||
(runtime->channels > 10)) {
dev_err(reader->dev, "%s: invalid nb of channels", __func__);
return -EINVAL;
}
SET_UNIPERIF_I2S_FMT_NUM_CH(reader, runtime->channels / 2);
SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader);
return 0;
}
static int uni_reader_prepare_tdm(struct snd_pcm_runtime *runtime,
struct uniperif *reader)
{
int frame_size; /* user tdm frame size in bytes */
/* default unip TDM_WORD_POS_X_Y */
unsigned int word_pos[4] = {
0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A};
frame_size = sti_uniperiph_get_user_frame_size(runtime);
/* fix 16/0 format */
SET_UNIPERIF_CONFIG_MEM_FMT_16_0(reader);
SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(reader);
/* number of words inserted on the TDM line */
SET_UNIPERIF_I2S_FMT_NUM_CH(reader, frame_size / 4 / 2);
SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader);
SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader);
SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(reader);
/*
* set the timeslots allocation for words in FIFO
*
* HW bug: (LSB word < MSB word) => this config is not possible
* So if we want (LSB word < MSB) word, then it shall be
* handled by user
*/
sti_uniperiph_get_tdm_word_pos(reader, word_pos);
SET_UNIPERIF_TDM_WORD_POS(reader, 1_2, word_pos[WORD_1_2]);
SET_UNIPERIF_TDM_WORD_POS(reader, 3_4, word_pos[WORD_3_4]);
SET_UNIPERIF_TDM_WORD_POS(reader, 5_6, word_pos[WORD_5_6]);
SET_UNIPERIF_TDM_WORD_POS(reader, 7_8, word_pos[WORD_7_8]);
return 0;
}
static int uni_reader_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
struct uniperif *reader = priv->dai_data.uni;
struct snd_pcm_runtime *runtime = substream->runtime;
int transfer_size, trigger_limit, ret;
int count = 10;
/* The reader should be stopped */
if (reader->state != UNIPERIF_STATE_STOPPED) {
dev_err(reader->dev, "%s: invalid reader state %d", __func__,
reader->state);
return -EINVAL;
}
/* Calculate transfer size (in fifo cells and bytes) for frame count */
if (reader->info->type == SND_ST_UNIPERIF_TYPE_TDM) {
/* transfer size = unip frame size (in 32 bits FIFO cell) */
transfer_size =
sti_uniperiph_get_user_frame_size(runtime) / 4;
} else {
transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES;
}
/* Calculate number of empty cells available before asserting DREQ */
if (reader->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size;
else
/*
* Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0
* FDMA_TRIGGER_LIMIT also controls when the state switches
* from OFF or STANDBY to AUDIO DATA.
*/
trigger_limit = transfer_size;
/* Trigger limit must be an even number */
if ((!trigger_limit % 2) ||
(trigger_limit != 1 && transfer_size % 2) ||
(trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(reader))) {
dev_err(reader->dev, "invalid trigger limit %d", trigger_limit);
return -EINVAL;
}
SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(reader, trigger_limit);
if (UNIPERIF_TYPE_IS_TDM(reader))
ret = uni_reader_prepare_tdm(runtime, reader);
else
ret = uni_reader_prepare_pcm(runtime, reader);
if (ret)
return ret;
switch (reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader);
@ -191,21 +249,26 @@ static int uni_reader_prepare(struct snd_pcm_substream *substream,
return -EINVAL;
}
SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader);
/* Data clocking (changing) on the rising edge */
SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader);
/* Number of channels must be even */
if ((runtime->channels % 2) || (runtime->channels < 2) ||
(runtime->channels > 10)) {
dev_err(reader->dev, "%s: invalid nb of channels", __func__);
return -EINVAL;
/* Data clocking (changing) on the rising/falling edge */
switch (reader->daifmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader);
SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader);
break;
case SND_SOC_DAIFMT_NB_IF:
SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader);
SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader);
break;
case SND_SOC_DAIFMT_IB_NF:
SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader);
SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(reader);
break;
case SND_SOC_DAIFMT_IB_IF:
SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader);
SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(reader);
break;
}
SET_UNIPERIF_I2S_FMT_NUM_CH(reader, runtime->channels / 2);
/* Clear any pending interrupts */
SET_UNIPERIF_ITS_BCLR(reader, GET_UNIPERIF_ITS(reader));
@ -293,6 +356,32 @@ static int uni_reader_trigger(struct snd_pcm_substream *substream,
}
}
static int uni_reader_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
struct uniperif *reader = priv->dai_data.uni;
int ret;
if (!UNIPERIF_TYPE_IS_TDM(reader))
return 0;
/* refine hw constraint in tdm mode */
ret = snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
sti_uniperiph_fix_tdm_chan,
reader, SNDRV_PCM_HW_PARAM_CHANNELS,
-1);
if (ret < 0)
return ret;
return snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_FORMAT,
sti_uniperiph_fix_tdm_format,
reader, SNDRV_PCM_HW_PARAM_FORMAT,
-1);
}
static void uni_reader_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
@ -310,6 +399,7 @@ static int uni_reader_parse_dt(struct platform_device *pdev,
{
struct uniperif_info *info;
struct device_node *node = pdev->dev.of_node;
const char *mode;
/* Allocate memory for the info structure */
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
@ -322,6 +412,17 @@ static int uni_reader_parse_dt(struct platform_device *pdev,
return -EINVAL;
}
/* Read the device mode property */
if (of_property_read_string(node, "st,mode", &mode)) {
dev_err(&pdev->dev, "uniperipheral mode not defined");
return -EINVAL;
}
if (strcasecmp(mode, "tdm") == 0)
info->type = SND_ST_UNIPERIF_TYPE_TDM;
else
info->type = SND_ST_UNIPERIF_TYPE_PCM;
/* Save the info structure */
reader->info = info;
@ -329,11 +430,13 @@ static int uni_reader_parse_dt(struct platform_device *pdev,
}
static const struct snd_soc_dai_ops uni_reader_dai_ops = {
.startup = uni_reader_startup,
.shutdown = uni_reader_shutdown,
.prepare = uni_reader_prepare,
.trigger = uni_reader_trigger,
.hw_params = sti_uniperiph_dai_hw_params,
.set_fmt = sti_uniperiph_dai_set_fmt,
.set_tdm_slot = sti_uniperiph_set_tdm_slot
};
int uni_reader_init(struct platform_device *pdev,
@ -343,7 +446,6 @@ int uni_reader_init(struct platform_device *pdev,
reader->dev = &pdev->dev;
reader->state = UNIPERIF_STATE_STOPPED;
reader->hw = &uni_reader_pcm_hw;
reader->dai_ops = &uni_reader_dai_ops;
ret = uni_reader_parse_dt(pdev, reader);
@ -352,6 +454,11 @@ int uni_reader_init(struct platform_device *pdev,
return ret;
}
if (UNIPERIF_TYPE_IS_TDM(reader))
reader->hw = &uni_tdm_hw;
else
reader->hw = &uni_reader_pcm_hw;
ret = devm_request_irq(&pdev->dev, reader->irq,
uni_reader_irq_handler, IRQF_SHARED,
dev_name(&pdev->dev), reader);