diff options
Diffstat (limited to 'sound')
144 files changed, 37549 insertions, 1215 deletions
diff --git a/sound/soc/atmel/atmel-classd.c b/sound/soc/atmel/atmel-classd.c index e98601eccfa3..0f2c574f27f1 100644 --- a/sound/soc/atmel/atmel-classd.c +++ b/sound/soc/atmel/atmel-classd.c @@ -571,8 +571,11 @@ static int atmel_classd_probe(struct platform_device *pdev) dd->pdata = pdata; dd->irq = platform_get_irq(pdev, 0); - if (dd->irq < 0) - return dd->irq; + if (dd->irq < 0) { + ret = dd->irq; + dev_err(dev, "failed to could not get irq: %d\n", ret); + return ret; + } dd->pclk = devm_clk_get(dev, "pclk"); if (IS_ERR(dd->pclk)) { diff --git a/sound/soc/atmel/atmel-pdmic.c b/sound/soc/atmel/atmel-pdmic.c index 04ec6f0af179..e09c28349e0d 100644 --- a/sound/soc/atmel/atmel-pdmic.c +++ b/sound/soc/atmel/atmel-pdmic.c @@ -612,8 +612,11 @@ static int atmel_pdmic_probe(struct platform_device *pdev) dd->dev = dev; dd->irq = platform_get_irq(pdev, 0); - if (dd->irq < 0) - return dd->irq; + if (dd->irq < 0) { + ret = dd->irq; + dev_err(dev, "failed to get irq: %d\n", ret); + return ret; + } dd->pclk = devm_clk_get(dev, "pclk"); if (IS_ERR(dd->pclk)) { diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c index 2f9357d7da96..b7c358b48d8d 100644 --- a/sound/soc/bcm/cygnus-ssp.c +++ b/sound/soc/bcm/cygnus-ssp.c @@ -1342,8 +1342,11 @@ static int cygnus_ssp_probe(struct platform_device *pdev) } cygaud->irq_num = platform_get_irq(pdev, 0); - if (cygaud->irq_num <= 0) - return cygaud->irq_num; + if (cygaud->irq_num <= 0) { + dev_err(dev, "platform_get_irq failed\n"); + err = cygaud->irq_num; + return err; + } err = audio_clk_init(pdev, cygaud); if (err) { diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 229cc89f8c5a..b889cb54461d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -257,6 +257,10 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM9705 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW) select SND_SOC_WM9712 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW) select SND_SOC_WM9713 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW) + select SND_SOC_RPMSG_WM8960 + select SND_SOC_RPMSG_WM8960_I2C + select SND_SOC_RPMSG_CS42XX8 + select SND_SOC_RPMSG_AK4497 help Normally ASoC codec drivers are only built if a machine driver which uses them is also built since they are only usable with a machine @@ -696,6 +700,9 @@ config SND_SOC_ES8328_SPI depends on SPI_MASTER select SND_SOC_ES8328 +config SND_SOC_FSL_MQS + tristate + config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' @@ -1436,6 +1443,18 @@ config SND_SOC_ZX_AUD96P22 depends on I2C select REGMAP_I2C +config SND_SOC_RPMSG_WM8960 + tristate "RPMSG WM8960 Platform Driver" + select SND_SOC_RPMSG_WM8960_I2C + +config SND_SOC_RPMSG_WM8960_I2C + tristate "RPMSG WM8960 I2C Driver" + +config SND_SOC_RPMSG_CS42XX8 + tristate + +config SND_SOC_RPMSG_AK4497 + tristate # Amp config SND_SOC_LM4857 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index c498373dcc5f..7323c2cc85bb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -86,6 +86,7 @@ snd-soc-es8316-objs := es8316.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o +snd-soc-fsl-mqs-objs := fsl_mqs.o snd-soc-gtm601-objs := gtm601.o snd-soc-hdac-hdmi-objs := hdac_hdmi.o snd-soc-hdac-hda-objs := hdac_hda.o @@ -274,6 +275,11 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-zx-aud96p22-objs := zx_aud96p22.o +snd-soc-rpmsg-wm8960-objs := rpmsg_wm8960.o +snd-soc-rpmsg-wm8960-i2c-objs := rpmsg_wm8960_i2c.o +snd-soc-rpmsg-cs42xx8-objs := rpmsg_cs42xx8.o +snd-soc-rpmsg-ak4497-objs := rpmsg_ak4497.o + # Amp snd-soc-max9877-objs := max9877.o snd-soc-max98504-objs := max98504.o @@ -370,6 +376,7 @@ obj-$(CONFIG_SND_SOC_ES8316) += snd-soc-es8316.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o +obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o @@ -557,6 +564,10 @@ obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_ZX_AUD96P22) += snd-soc-zx-aud96p22.o +obj-$(CONFIG_SND_SOC_RPMSG_WM8960) += snd-soc-rpmsg-wm8960.o +obj-$(CONFIG_SND_SOC_RPMSG_WM8960_I2C) += snd-soc-rpmsg-wm8960-i2c.o +obj-$(CONFIG_SND_SOC_RPMSG_CS42XX8) += snd-soc-rpmsg-cs42xx8.o +obj-$(CONFIG_SND_SOC_RPMSG_AK4497) += snd-soc-rpmsg-ak4497.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c index eca5fc559d69..bbb650532941 100644 --- a/sound/soc/codecs/ak4458.c +++ b/sound/soc/codecs/ak4458.c @@ -18,12 +18,20 @@ #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/tlv.h> +#include <linux/regulator/consumer.h> #include "ak4458.h" +#define AK4458_NUM_SUPPLIES 2 +static const char *ak4458_supply_names[AK4458_NUM_SUPPLIES] = { + "DVDD", + "AVDD", +}; + struct ak4458_drvdata { struct snd_soc_dai_driver *dai_drv; const struct snd_soc_component_driver *comp_drv; + bool dsd512; /* DSD512 is supported or not */ }; /* AK4458 Codec Private Data */ @@ -32,11 +40,13 @@ struct ak4458_priv { struct regmap *regmap; struct gpio_desc *reset_gpiod; struct gpio_desc *mute_gpiod; + const struct ak4458_drvdata *drvdata; int digfil; /* SSLOW, SD, SLOW bits */ int fs; /* sampling rate */ int fmt; int slots; int slot_width; + struct regulator_bulk_data supplies[AK4458_NUM_SUPPLIES]; }; static const struct reg_default ak4458_reg_defaults[] = { @@ -128,6 +138,9 @@ static const char * const ak4458_ats_select_texts[] = { /* DIF2 bit Audio Interface Format Setting(BICK fs) */ static const char * const ak4458_dif_select_texts[] = {"32fs,48fs", "64fs",}; +static const char * const ak4497_dsd_input_path_select[] = { + "16_17_19pin", "3_4_5pin"}; + static const struct soc_enum ak4458_dac1_dem_enum = SOC_ENUM_SINGLE(AK4458_01_CONTROL2, 1, ARRAY_SIZE(ak4458_dem_select_texts), @@ -167,6 +180,10 @@ static const struct soc_enum ak4458_dif_enum = SOC_ENUM_SINGLE(AK4458_00_CONTROL1, 3, ARRAY_SIZE(ak4458_dif_select_texts), ak4458_dif_select_texts); +static const struct soc_enum ak4497_dsdp_enum = + SOC_ENUM_SINGLE(AK4458_09_DSD2, 2, + ARRAY_SIZE(ak4497_dsd_input_path_select), + ak4497_dsd_input_path_select); static int get_digfil(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -274,6 +291,7 @@ static const struct snd_kcontrol_new ak4497_snd_controls[] = { SOC_ENUM("AK4497 Sound Mode", ak4458_sm_enum), SOC_ENUM("AK4497 Attenuation transition Time Setting", ak4458_ats_enum), + SOC_ENUM("AK4497 DSD Data Input Pin", ak4497_dsdp_enum), }; /* ak4497 dapm widgets */ @@ -290,6 +308,20 @@ static const struct snd_soc_dapm_route ak4497_intercon[] = { }; +static int ak4458_get_tdm_mode(struct ak4458_priv *ak4458) +{ + switch (ak4458->slots * ak4458->slot_width) { + case 128: + return 1; + case 256: + return 2; + case 512: + return 3; + default: + return 0; + } +} + static int ak4458_rstn_control(struct snd_soc_component *component, int bit) { int ret; @@ -318,11 +350,60 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream, struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); int pcm_width = max(params_physical_width(params), ak4458->slot_width); int nfs1; - u8 format; + u8 format, dsdsel0, dsdsel1, dchn; + int ret, dsd_bclk, channels, channels_max; + bool is_dsd = false; + + channels = params_channels(params); + channels_max = dai->driver->playback.channels_max; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + is_dsd = true; + dsd_bclk = params_rate(params) * params_physical_width(params); + break; + } nfs1 = params_rate(params); ak4458->fs = nfs1; + if (is_dsd) { + switch (dsd_bclk) { + case 2822400: + dsdsel0 = 0; + dsdsel1 = 0; + break; + case 5644800: + dsdsel0 = 1; + dsdsel1 = 0; + break; + case 11289600: + dsdsel0 = 0; + dsdsel1 = 1; + break; + case 22579200: + if (ak4458->drvdata->dsd512) { + dsdsel0 = 1; + dsdsel1 = 1; + } else { + dev_err(dai->dev, "DSD512 not supported.\n"); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, AK4458_06_DSD1, + AK4458_DSDSEL_MASK, dsdsel0); + snd_soc_component_update_bits(component, AK4458_09_DSD2, + AK4458_DSDSEL_MASK, dsdsel1); + } + /* Master Clock Frequency Auto Setting Mode Enable */ snd_soc_component_update_bits(component, AK4458_00_CONTROL1, 0x80, 0x80); @@ -347,6 +428,9 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream, case SND_SOC_DAIFMT_DSP_B: format = AK4458_DIF_32BIT_MSB; break; + case SND_SOC_DAIFMT_PDM: + format = AK4458_DIF_32BIT_MSB; + break; default: return -EINVAL; } @@ -358,8 +442,24 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream, snd_soc_component_update_bits(component, AK4458_00_CONTROL1, AK4458_DIF_MASK, format); - ak4458_rstn_control(component, 0); - ak4458_rstn_control(component, 1); + /* + * Enable/disable Daisy Chain if in TDM mode and the number of played + * channels is bigger than the maximum supported number of channels + */ + dchn = ak4458_get_tdm_mode(ak4458) && + (ak4458->fmt == SND_SOC_DAIFMT_DSP_B) && + (channels > channels_max) ? AK4458_DCHAIN_MASK : 0; + + snd_soc_component_update_bits(component, AK4458_0B_CONTROL7, + AK4458_DCHAIN_MASK, dchn); + + ret = ak4458_rstn_control(component, 0); + if (ret) + return ret; + + ret = ak4458_rstn_control(component, 1); + if (ret) + return ret; return 0; } @@ -368,6 +468,7 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_component *component = dai->component; struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + u8 dp; switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: /* Slave Mode */ @@ -385,6 +486,7 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) case SND_SOC_DAIFMT_LEFT_J: case SND_SOC_DAIFMT_RIGHT_J: case SND_SOC_DAIFMT_DSP_B: + case SND_SOC_DAIFMT_PDM: ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; break; default: @@ -393,6 +495,11 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } + /* DSD mode */ + dp = ak4458->fmt == SND_SOC_DAIFMT_PDM ? AK4458_DP_MASK : 0; + snd_soc_component_update_bits(component, AK4458_02_CONTROL3, + AK4458_DP_MASK, dp); + ak4458_rstn_control(component, 0); ak4458_rstn_control(component, 1); @@ -440,31 +547,20 @@ static int ak4458_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, ak4458->slots = slots; ak4458->slot_width = slot_width; - switch (slots * slot_width) { - case 128: - mode = AK4458_MODE_TDM128; - break; - case 256: - mode = AK4458_MODE_TDM256; - break; - case 512: - mode = AK4458_MODE_TDM512; - break; - default: - mode = AK4458_MODE_NORMAL; - break; - } + mode = ak4458_get_tdm_mode(ak4458) << AK4458_MODE_SHIFT; snd_soc_component_update_bits(component, AK4458_0A_CONTROL6, AK4458_MODE_MASK, mode); - return 0; } #define AK4458_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ SNDRV_PCM_FMTBIT_S24_LE |\ - SNDRV_PCM_FMTBIT_S32_LE) + SNDRV_PCM_FMTBIT_S32_LE |\ + SNDRV_PCM_FMTBIT_DSD_U8 |\ + SNDRV_PCM_FMTBIT_DSD_U16_LE |\ + SNDRV_PCM_FMTBIT_DSD_U32_LE) static const unsigned int ak4458_rates[] = { 8000, 11025, 16000, 22050, @@ -641,11 +737,13 @@ static const struct regmap_config ak4458_regmap = { static const struct ak4458_drvdata ak4458_drvdata = { .dai_drv = &ak4458_dai, .comp_drv = &soc_codec_dev_ak4458, + .dsd512 = false, }; static const struct ak4458_drvdata ak4497_drvdata = { .dai_drv = &ak4497_dai, .comp_drv = &soc_codec_dev_ak4497, + .dsd512 = true, }; static const struct dev_pm_ops ak4458_pm = { @@ -657,8 +755,8 @@ static const struct dev_pm_ops ak4458_pm = { static int ak4458_i2c_probe(struct i2c_client *i2c) { struct ak4458_priv *ak4458; - const struct ak4458_drvdata *drvdata; int ret; + int i; ak4458 = devm_kzalloc(&i2c->dev, sizeof(*ak4458), GFP_KERNEL); if (!ak4458) @@ -671,7 +769,7 @@ static int ak4458_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, ak4458); ak4458->dev = &i2c->dev; - drvdata = of_device_get_match_data(&i2c->dev); + ak4458->drvdata = of_device_get_match_data(&i2c->dev); ak4458->reset_gpiod = devm_gpiod_get_optional(ak4458->dev, "reset", GPIOD_OUT_LOW); @@ -683,8 +781,25 @@ static int ak4458_i2c_probe(struct i2c_client *i2c) if (IS_ERR(ak4458->mute_gpiod)) return PTR_ERR(ak4458->mute_gpiod); - ret = devm_snd_soc_register_component(ak4458->dev, drvdata->comp_drv, - drvdata->dai_drv, 1); + for (i = 0; i < ARRAY_SIZE(ak4458->supplies); i++) + ak4458->supplies[i].supply = ak4458_supply_names[i]; + + ret = devm_regulator_bulk_get(ak4458->dev, ARRAY_SIZE(ak4458->supplies), + ak4458->supplies); + if (ret != 0) { + dev_err(ak4458->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ak4458->supplies), + ak4458->supplies); + if (ret != 0) { + dev_err(ak4458->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(ak4458->dev, ak4458->drvdata->comp_drv, + ak4458->drvdata->dai_drv, 1); if (ret < 0) { dev_err(ak4458->dev, "Failed to register CODEC: %d\n", ret); return ret; diff --git a/sound/soc/codecs/ak4458.h b/sound/soc/codecs/ak4458.h index f906215f7e4e..f4cf00720dd7 100644 --- a/sound/soc/codecs/ak4458.h +++ b/sound/soc/codecs/ak4458.h @@ -83,4 +83,9 @@ #define AK4458_ATS_SHIFT 6 #define AK4458_ATS_MASK GENMASK(7, 6) -#endif /* _AK4458_H */ +#define AK4458_DSDSEL_MASK (0x1 << 0) +#define AK4458_DP_MASK (0x1 << 7) + +#define AK4458_DCHAIN_MASK (0x1 << 1) + +#endif diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c index e98312a6b840..a856031b450d 100644 --- a/sound/soc/codecs/ak5558.c +++ b/sound/soc/codecs/ak5558.c @@ -9,6 +9,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/module.h> +#include <linux/of_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/slab.h> @@ -19,9 +20,21 @@ #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/tlv.h> +#include <linux/regulator/consumer.h> #include "ak5558.h" +#define AK5558_SLAVE_CKS_AUTO + +/* enable debug */ +/* #define AK5558_DEBUG */ + +#define AK5558_NUM_SUPPLIES 2 +static const char *ak5558_supply_names[AK5558_NUM_SUPPLIES] = { + "DVDD", + "AVDD", +}; + /* AK5558 Codec Private Data */ struct ak5558_priv { struct snd_soc_component component; @@ -30,6 +43,7 @@ struct ak5558_priv { struct gpio_desc *reset_gpiod; /* Reset & Power down GPIO */ int slots; int slot_width; + struct regulator_bulk_data supplies[AK5558_NUM_SUPPLIES]; }; /* ak5558 register cache & default register settings */ @@ -51,9 +65,18 @@ static const struct soc_enum ak5558_mono_enum[] = { ARRAY_SIZE(mono_texts), mono_texts), }; +static const char * const mono_5552_texts[] = { + "2 Slot", "1 Slot (Fixed)", "2 Slot", "1 Slot (Optimal)", +}; + +static const struct soc_enum ak5552_mono_enum[] = { + SOC_ENUM_SINGLE(AK5558_01_POWER_MANAGEMENT2, 1, + ARRAY_SIZE(mono_5552_texts), mono_5552_texts), +}; + static const char * const digfil_texts[] = { - "Sharp Roll-Off", "Show Roll-Off", - "Short Delay Sharp Roll-Off", "Short Delay Show Roll-Off", + "Sharp Roll-Off", "Slow Roll-Off", + "Short Delay Sharp Roll-Off", "Short Delay Slow Roll-Off", }; static const struct soc_enum ak5558_adcset_enum[] = { @@ -66,6 +89,11 @@ static const struct snd_kcontrol_new ak5558_snd_controls[] = { SOC_ENUM("AK5558 Digital Filter", ak5558_adcset_enum[0]), }; +static const struct snd_kcontrol_new ak5552_snd_controls[] = { + SOC_ENUM("AK5552 Monaural Mode", ak5552_mono_enum[0]), + SOC_ENUM("AK5552 Digital Filter", ak5558_adcset_enum[0]), +}; + static const struct snd_soc_dapm_widget ak5558_dapm_widgets[] = { /* Analog Input */ SND_SOC_DAPM_INPUT("AIN1"), @@ -89,6 +117,17 @@ static const struct snd_soc_dapm_widget ak5558_dapm_widgets[] = { SND_SOC_DAPM_AIF_OUT("SDTO", "Capture", 0, SND_SOC_NOPM, 0, 0), }; +static const struct snd_soc_dapm_widget ak5552_dapm_widgets[] = { + /* Analog Input */ + SND_SOC_DAPM_INPUT("AIN1"), + SND_SOC_DAPM_INPUT("AIN2"), + + SND_SOC_DAPM_ADC("ADC Ch1", NULL, AK5558_00_POWER_MANAGEMENT1, 0, 0), + SND_SOC_DAPM_ADC("ADC Ch2", NULL, AK5558_00_POWER_MANAGEMENT1, 1, 0), + + SND_SOC_DAPM_AIF_OUT("SDTO", "Capture", 0, SND_SOC_NOPM, 0, 0), +}; + static const struct snd_soc_dapm_route ak5558_intercon[] = { {"ADC Ch1", NULL, "AIN1"}, {"SDTO", NULL, "ADC Ch1"}, @@ -115,6 +154,14 @@ static const struct snd_soc_dapm_route ak5558_intercon[] = { {"SDTO", NULL, "ADC Ch8"}, }; +static const struct snd_soc_dapm_route ak5552_intercon[] = { + {"ADC Ch1", NULL, "AIN1"}, + {"SDTO", NULL, "ADC Ch1"}, + + {"ADC Ch2", NULL, "AIN2"}, + {"SDTO", NULL, "ADC Ch2"}, +}; + static int ak5558_set_mcki(struct snd_soc_component *component) { return snd_soc_component_update_bits(component, AK5558_02_CONTROL1, AK5558_CKS, @@ -259,6 +306,18 @@ static struct snd_soc_dai_driver ak5558_dai = { .ops = &ak5558_dai_ops, }; +static struct snd_soc_dai_driver ak5552_dai = { + .name = "ak5558-aif", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = AK5558_FORMATS, + }, + .ops = &ak5558_dai_ops, +}; + static void ak5558_power_off(struct ak5558_priv *ak5558) { if (!ak5558->reset_gpiod) @@ -336,6 +395,21 @@ static const struct snd_soc_component_driver soc_codec_dev_ak5558 = { .non_legacy_dai_naming = 1, }; +static const struct snd_soc_component_driver soc_codec_dev_ak5552 = { + .probe = ak5558_probe, + .remove = ak5558_remove, + .controls = ak5552_snd_controls, + .num_controls = ARRAY_SIZE(ak5552_snd_controls), + .dapm_widgets = ak5552_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak5552_dapm_widgets), + .dapm_routes = ak5552_intercon, + .num_dapm_routes = ARRAY_SIZE(ak5552_intercon), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + static const struct regmap_config ak5558_regmap = { .reg_bits = 8, .val_bits = 8, @@ -350,6 +424,7 @@ static int ak5558_i2c_probe(struct i2c_client *i2c) { struct ak5558_priv *ak5558; int ret = 0; + int i; ak5558 = devm_kzalloc(&i2c->dev, sizeof(*ak5558), GFP_KERNEL); if (!ak5558) @@ -367,9 +442,31 @@ static int ak5558_i2c_probe(struct i2c_client *i2c) if (IS_ERR(ak5558->reset_gpiod)) return PTR_ERR(ak5558->reset_gpiod); - ret = devm_snd_soc_register_component(&i2c->dev, - &soc_codec_dev_ak5558, - &ak5558_dai, 1); + for (i = 0; i < ARRAY_SIZE(ak5558->supplies); i++) + ak5558->supplies[i].supply = ak5558_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(ak5558->supplies), + ak5558->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ak5558->supplies), + ak5558->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + if (of_device_is_compatible(i2c->dev.of_node, "asahi-kasei,ak5552")) + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_codec_dev_ak5552, + &ak5552_dai, 1); + else + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_codec_dev_ak5558, + &ak5558_dai, 1); if (ret) return ret; @@ -386,7 +483,8 @@ static int ak5558_i2c_remove(struct i2c_client *i2c) } static const struct of_device_id ak5558_i2c_dt_ids[] = { - { .compatible = "asahi-kasei,ak5558"}, + { .compatible = "asahi-kasei,ak5558", }, + { .compatible = "asahi-kasei,ak5552", }, { } }; MODULE_DEVICE_TABLE(of, ak5558_i2c_dt_ids); diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index 94b1adb088fd..d69b457c3dac 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -43,7 +43,8 @@ struct cs42xx8_priv { struct regmap *regmap; struct clk *clk; - bool slave_mode; + bool slave_mode[2]; + bool is_tdm; unsigned long sysclk; u32 tx_channels; struct gpio_desc *gpiod_reset; @@ -131,7 +132,6 @@ static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = { SND_SOC_DAPM_INPUT("AIN2L"), SND_SOC_DAPM_INPUT("AIN2R"), - SND_SOC_DAPM_SUPPLY("PWR", CS42XX8_PWRCTL, 0, 1, NULL, 0), }; static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = { @@ -145,35 +145,28 @@ static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = { /* Playback */ { "AOUT1L", NULL, "DAC1" }, { "AOUT1R", NULL, "DAC1" }, - { "DAC1", NULL, "PWR" }, { "AOUT2L", NULL, "DAC2" }, { "AOUT2R", NULL, "DAC2" }, - { "DAC2", NULL, "PWR" }, { "AOUT3L", NULL, "DAC3" }, { "AOUT3R", NULL, "DAC3" }, - { "DAC3", NULL, "PWR" }, { "AOUT4L", NULL, "DAC4" }, { "AOUT4R", NULL, "DAC4" }, - { "DAC4", NULL, "PWR" }, /* Capture */ { "ADC1", NULL, "AIN1L" }, { "ADC1", NULL, "AIN1R" }, - { "ADC1", NULL, "PWR" }, { "ADC2", NULL, "AIN2L" }, { "ADC2", NULL, "AIN2R" }, - { "ADC2", NULL, "PWR" }, }; static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = { /* Capture */ { "ADC3", NULL, "AIN3L" }, { "ADC3", NULL, "AIN3R" }, - { "ADC3", NULL, "PWR" }, }; struct cs42xx8_ratios { @@ -218,6 +211,8 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); u32 val; + cs42xx8->is_tdm = false; + /* Set DAI format */ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_LEFT_J: @@ -231,6 +226,7 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, break; case SND_SOC_DAIFMT_DSP_A: val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM; + cs42xx8->is_tdm = true; break; default: dev_err(component->dev, "unsupported dai format\n"); @@ -241,17 +237,21 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, CS42XX8_INTF_DAC_DIF_MASK | CS42XX8_INTF_ADC_DIF_MASK, val); - /* Set master/slave audio interface */ - switch (format & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - cs42xx8->slave_mode = true; - break; - case SND_SOC_DAIFMT_CBM_CFM: - cs42xx8->slave_mode = false; - break; - default: - dev_err(component->dev, "unsupported master/slave mode\n"); - return -EINVAL; + if (cs42xx8->slave_mode[0] == cs42xx8->slave_mode[1]) { + /* Set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs42xx8->slave_mode[0] = true; + cs42xx8->slave_mode[1] = true; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs42xx8->slave_mode[0] = false; + cs42xx8->slave_mode[1] = false; + break; + default: + dev_err(component->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } } return 0; @@ -281,7 +281,7 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream, /* Get functional mode for tx and rx according to rate */ for (i = 0; i < 2; i++) { - if (cs42xx8->slave_mode) { + if (cs42xx8->slave_mode[i]) { fm[i] = CS42XX8_FM_AUTO; } else { if (rate[i] < 50000) { @@ -336,6 +336,16 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream, cs42xx8->rate[tx] = params_rate(params); + if (cs42xx8->is_tdm && !cs42xx8->slave_mode[tx]) { + dev_err(component->dev, "TDM mode is unsupported in master mode\n"); + return -EINVAL; + } + + if (cs42xx8->is_tdm && (cs42xx8->sysclk < 256 * cs42xx8->rate[tx])) { + dev_err(component->dev, "unsupported sysclk for TDM mode\n"); + return -EINVAL; + } + mask = CS42XX8_FUNCMOD_MFREQ_MASK; val = cs42xx8_ratios[i].mfreq; @@ -459,6 +469,8 @@ const struct regmap_config cs42xx8_regmap_config = { .volatile_reg = cs42xx8_volatile_register, .writeable_reg = cs42xx8_writeable_register, .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, }; EXPORT_SYMBOL_GPL(cs42xx8_regmap_config); @@ -482,7 +494,8 @@ static int cs42xx8_component_probe(struct snd_soc_component *component) /* Mute all DAC channels */ regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL); - + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 0); return 0; } @@ -563,6 +576,18 @@ int cs42xx8_probe(struct device *dev, struct regmap *regmap) cs42xx8->sysclk = clk_get_rate(cs42xx8->clk); + if (of_property_read_bool(dev->of_node, "fsl,txm-rxs")) { + /* 0 -- rx, 1 -- tx */ + cs42xx8->slave_mode[0] = true; + cs42xx8->slave_mode[1] = false; + } + + if (of_property_read_bool(dev->of_node, "fsl,txs-rxm")) { + /* 0 -- rx, 1 -- tx */ + cs42xx8->slave_mode[0] = false; + cs42xx8->slave_mode[1] = true; + } + for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++) cs42xx8->supplies[i].supply = cs42xx8_supply_names[i]; diff --git a/sound/soc/codecs/fsl_mqs.c b/sound/soc/codecs/fsl_mqs.c new file mode 100644 index 000000000000..c82d889bfabb --- /dev/null +++ b/sound/soc/codecs/fsl_mqs.c @@ -0,0 +1,357 @@ +/* + * ALSA SoC IMX MQS driver + * + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. + * + * 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/clk.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/initval.h> + + +#define REG_MQS_CTRL 0x00 + +#define MQS_EN_MASK (0x1 << 28) +#define MQS_EN_SHIFT (28) +#define MQS_SW_RST_MASK (0x1 << 24) +#define MQS_SW_RST_SHIFT (24) +#define MQS_OVERSAMPLE_MASK (0x1 << 20) +#define MQS_OVERSAMPLE_SHIFT (20) +#define MQS_CLK_DIV_MASK (0xFF << 0) +#define MQS_CLK_DIV_SHIFT (0) + + +/* codec private data */ +struct fsl_mqs { + struct platform_device *pdev; + struct regmap *gpr; + unsigned int reg_iomuxc_gpr2; + + struct regmap *regmap; + unsigned int reg_mqs_ctrl; + + struct clk *mclk; + struct clk *ipg; + + unsigned long mclk_rate; + + int sysclk_rate; + int bclk; + int lrclk; + bool use_gpr; + char name[32]; +}; + +#define FSL_MQS_RATES SNDRV_PCM_RATE_8000_192000 +#define FSL_MQS_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static int fsl_mqs_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component); + int div, res; + + mqs_priv->mclk_rate = clk_get_rate(mqs_priv->mclk); + + mqs_priv->bclk = snd_soc_params_to_bclk(params); + mqs_priv->lrclk = params_rate(params); + + /* + * mclk_rate / (oversample(32,64) * FS * 2 * divider ) = repeat_rate; + * if repeat_rate is 8, mqs can achieve better quality. + * oversample rate is fix to 32 currently. + */ + div = mqs_priv->mclk_rate / (32 * 2 * mqs_priv->lrclk * 8); + res = mqs_priv->mclk_rate % (32 * 2 * mqs_priv->lrclk * 8); + + if (res == 0 && div > 0 && div <= 256) { + if (mqs_priv->use_gpr) { + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_CLK_DIV_MASK, + (div-1) << IMX6SX_GPR2_MQS_CLK_DIV_SHIFT); + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_OVERSAMPLE_MASK, + 0 << IMX6SX_GPR2_MQS_OVERSAMPLE_SHIFT); + } else { + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_CLK_DIV_MASK, + (div-1) << MQS_CLK_DIV_SHIFT); + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_OVERSAMPLE_MASK, + 0 << MQS_OVERSAMPLE_SHIFT); + } + } else + dev_err(&mqs_priv->pdev->dev, "can't get proper divider\n"); + + return 0; +} + +static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_mqs_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component); + + mqs_priv->sysclk_rate = freq; + + return 0; +} + +static int fsl_mqs_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component); + + if (mqs_priv->use_gpr) + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, IMX6SX_GPR2_MQS_EN_MASK, + 1 << IMX6SX_GPR2_MQS_EN_SHIFT); + else + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_EN_MASK, + 1 << MQS_EN_SHIFT); + return 0; +} + +static void fsl_mqs_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component); + + if (mqs_priv->use_gpr) + regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, + IMX6SX_GPR2_MQS_EN_MASK, 0); + else + regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL, + MQS_EN_MASK, 0); +} + +static struct snd_soc_component_driver soc_codec_fsl_mqs = { + .idle_bias_on = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct snd_soc_dai_ops fsl_mqs_dai_ops = { + .startup = fsl_mqs_startup, + .shutdown = fsl_mqs_shutdown, + .hw_params = fsl_mqs_hw_params, + .set_fmt = fsl_mqs_set_dai_fmt, + .set_sysclk = fsl_mqs_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver fsl_mqs_dai = { + .name = "fsl-mqs-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_MQS_RATES, + .formats = FSL_MQS_FORMATS, + }, + .ops = &fsl_mqs_dai_ops, +}; + +static const struct regmap_config fsl_mqs_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = REG_MQS_CTRL, + .cache_type = REGCACHE_NONE, +}; + +static int fsl_mqs_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *gpr_np = 0; + struct fsl_mqs *mqs_priv; + struct resource *res; + void __iomem *regs; + int ret = 0; + + mqs_priv = devm_kzalloc(&pdev->dev, sizeof(*mqs_priv), GFP_KERNEL); + if (!mqs_priv) + return -ENOMEM; + + mqs_priv->pdev = pdev; + strncpy(mqs_priv->name, np->name, sizeof(mqs_priv->name) - 1); + + if (of_device_is_compatible(np, "fsl,imx8qm-mqs")) + mqs_priv->use_gpr = false; + else + mqs_priv->use_gpr = true; + + if (mqs_priv->use_gpr) { + gpr_np = of_parse_phandle(np, "gpr", 0); + if (IS_ERR(gpr_np)) { + dev_err(&pdev->dev, "failed to get gpr node by phandle\n"); + ret = PTR_ERR(gpr_np); + goto out; + } + + mqs_priv->gpr = syscon_node_to_regmap(gpr_np); + if (IS_ERR(mqs_priv->gpr)) { + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + ret = PTR_ERR(mqs_priv->gpr); + goto out; + } + } else { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mqs_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "core", regs, &fsl_mqs_regmap_config); + if (IS_ERR(mqs_priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap: %ld\n", + PTR_ERR(mqs_priv->regmap)); + return PTR_ERR(mqs_priv->regmap); + } + + mqs_priv->ipg = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(mqs_priv->ipg)) { + dev_err(&pdev->dev, "failed to get the clock: %ld\n", + PTR_ERR(mqs_priv->ipg)); + goto out; + } + } + + mqs_priv->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(mqs_priv->mclk)) { + dev_err(&pdev->dev, "failed to get the clock: %ld\n", + PTR_ERR(mqs_priv->mclk)); + goto out; + } + + dev_set_drvdata(&pdev->dev, mqs_priv); + pm_runtime_enable(&pdev->dev); + + return devm_snd_soc_register_component(&pdev->dev, &soc_codec_fsl_mqs, + &fsl_mqs_dai, 1); +out: + if (!IS_ERR(gpr_np)) + of_node_put(gpr_np); + + return ret; +} + +static int fsl_mqs_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +#ifdef CONFIG_PM +static int fsl_mqs_runtime_resume(struct device *dev) +{ + struct fsl_mqs *mqs_priv = dev_get_drvdata(dev); + + if (mqs_priv->ipg) + clk_prepare_enable(mqs_priv->ipg); + + if (mqs_priv->mclk) + clk_prepare_enable(mqs_priv->mclk); + + if (mqs_priv->use_gpr) + regmap_write(mqs_priv->gpr, IOMUXC_GPR2, + mqs_priv->reg_iomuxc_gpr2); + else + regmap_write(mqs_priv->regmap, REG_MQS_CTRL, + mqs_priv->reg_mqs_ctrl); + return 0; +} + +static int fsl_mqs_runtime_suspend(struct device *dev) +{ + struct fsl_mqs *mqs_priv = dev_get_drvdata(dev); + + if (mqs_priv->use_gpr) + regmap_read(mqs_priv->gpr, IOMUXC_GPR2, + &mqs_priv->reg_iomuxc_gpr2); + else + regmap_read(mqs_priv->regmap, REG_MQS_CTRL, + &mqs_priv->reg_mqs_ctrl); + + if (mqs_priv->mclk) + clk_disable_unprepare(mqs_priv->mclk); + + if (mqs_priv->ipg) + clk_disable_unprepare(mqs_priv->ipg); + + return 0; +} +#endif + +static const struct dev_pm_ops fsl_mqs_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_mqs_runtime_suspend, + fsl_mqs_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +static const struct of_device_id fsl_mqs_dt_ids[] = { + { .compatible = "fsl,imx8qm-mqs", }, + { .compatible = "fsl,imx6sx-mqs", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_mqs_dt_ids); + + +static struct platform_driver fsl_mqs_driver = { + .probe = fsl_mqs_probe, + .remove = fsl_mqs_remove, + .driver = { + .name = "fsl-mqs", + .of_match_table = fsl_mqs_dt_ids, + .pm = &fsl_mqs_pm_ops, + }, +}; + +module_platform_driver(fsl_mqs_driver); + +MODULE_AUTHOR("shengjiu wang <shengjiu.wang@freescale.com>"); +MODULE_DESCRIPTION("MQS dummy codec driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform: fsl-mqs"); diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index f8b5b960e597..cc67f79c8c3d 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -277,10 +277,12 @@ struct hdmi_codec_priv { unsigned long busy; struct snd_soc_jack *jack; unsigned int jack_status; + struct snd_aes_iec958 iec; }; static const struct snd_soc_dapm_widget hdmi_widgets[] = { SND_SOC_DAPM_OUTPUT("TX"), + SND_SOC_DAPM_OUTPUT("RX"), }; enum { @@ -384,10 +386,56 @@ static int hdmi_codec_chmap_ctl_get(struct snd_kcontrol *kcontrol, return 0; } +/* + * ALSA iec958 controls + */ +static int hdmi_codec_iec958_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int hdmi_codec_iec958_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(comp); + int i; + + for (i = 0; i < 24; i++) + ucontrol->value.iec958.status[i] = hcp->iec.status[i]; + + return 0; +} + +static int hdmi_codec_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(comp); + int i; + + for (i = 0; i < 24; i++) + hcp->iec.status[i] = ucontrol->value.iec958.status[i]; + + return 0; +} + +static const struct snd_kcontrol_new hdmi_codec_controls = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = hdmi_codec_iec958_info, + .get = hdmi_codec_iec958_get, + .put = hdmi_codec_iec958_put, +}; + static int hdmi_codec_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; int ret = 0; ret = test_and_set_bit(0, &hcp->busy); @@ -402,7 +450,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, goto err; } - if (hcp->hcd.ops->get_eld) { + if (tx && hcp->hcd.ops->get_eld) { ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, hcp->eld, sizeof(hcp->eld)); @@ -462,6 +510,15 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, return ret; } + if (hcp->iec.status[0] || hcp->iec.status[1] || hcp->iec.status[2] || + hcp->iec.status[3] || hcp->iec.status[4]) { + hp.iec.status[0] = hcp->iec.status[0]; + hp.iec.status[1] = hcp->iec.status[1]; + hp.iec.status[2] = hcp->iec.status[2]; + hp.iec.status[3] = hcp->iec.status[3]; + hp.iec.status[4] = hcp->iec.status[4]; + } + hdmi_audio_infoframe_init(&hp.cea); hp.cea.channels = params_channels(params); hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; @@ -471,13 +528,11 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, /* Select a channel allocation that matches with ELD and pcm channels */ idx = hdmi_codec_get_ch_alloc_table_idx(hcp, hp.cea.channels); if (idx < 0) { - dev_err(dai->dev, "Not able to map channels to speakers (%d)\n", - idx); hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; - return idx; + } else { + hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id; + hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id; } - hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id; - hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id; hp.sample_width = params_width(params); hp.sample_rate = params_rate(params); @@ -635,6 +690,14 @@ static int hdmi_codec_pcm_new(struct snd_soc_pcm_runtime *rtd, hcp->chmap_info->chmap = hdmi_codec_stereo_chmaps; hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + kctl = snd_ctl_new1(&hdmi_codec_controls, dai->component); + if (!kctl) + return -ENOMEM; + + ret = snd_ctl_add(rtd->card->snd_card, kctl); + if (ret < 0) + return ret; + /* add ELD ctl with the device number corresponding to the PCM stream */ kctl = snd_ctl_new1(&hdmi_eld_ctl, dai->component); if (!kctl) @@ -647,14 +710,20 @@ static int hdmi_dai_probe(struct snd_soc_dai *dai) { struct snd_soc_dapm_context *dapm; struct hdmi_codec_daifmt *daifmt; - struct snd_soc_dapm_route route = { - .sink = "TX", - .source = dai->driver->playback.stream_name, + struct snd_soc_dapm_route route[] = { + { + .sink = "TX", + .source = dai->driver->playback.stream_name, + }, + { + .sink = dai->driver->playback.stream_name, + .source = "RX", + }, }; int ret; dapm = snd_soc_component_get_dapm(dai->component); - ret = snd_soc_dapm_add_routes(dapm, &route, 1); + ret = snd_soc_dapm_add_routes(dapm, route, 2); if (ret) return ret; @@ -744,6 +813,14 @@ static const struct snd_soc_dai_driver hdmi_i2s_dai = { .sig_bits = 24, }, .ops = &hdmi_codec_i2s_dai_ops, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = HDMI_RATES, + .formats = I2S_FORMATS, + .sig_bits = 24, + }, .pcm_new = hdmi_codec_pcm_new, }; @@ -760,6 +837,13 @@ static const struct snd_soc_dai_driver hdmi_spdif_dai = { .formats = SPDIF_FORMATS, }, .ops = &hdmi_codec_spdif_dai_ops, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = HDMI_RATES, + .formats = SPDIF_FORMATS, + }, .pcm_new = hdmi_codec_pcm_new, }; diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c index 337bddb7c2a4..cf6516693e4e 100644 --- a/sound/soc/codecs/msm8916-wcd-analog.c +++ b/sound/soc/codecs/msm8916-wcd-analog.c @@ -1195,8 +1195,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) } irq = platform_get_irq_byname(pdev, "mbhc_switch_int"); - if (irq < 0) + if (irq < 0) { + dev_err(dev, "failed to get mbhc switch irq\n"); return irq; + } ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_mbhc_switch_irq_handler, @@ -1208,8 +1210,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) if (priv->mbhc_btn_enabled) { irq = platform_get_irq_byname(pdev, "mbhc_but_press_det"); - if (irq < 0) + if (irq < 0) { + dev_err(dev, "failed to get button press irq\n"); return irq; + } ret = devm_request_threaded_irq(dev, irq, NULL, mbhc_btn_press_irq_handler, @@ -1220,8 +1224,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) dev_err(dev, "cannot request mbhc button press irq\n"); irq = platform_get_irq_byname(pdev, "mbhc_but_rel_det"); - if (irq < 0) + if (irq < 0) { + dev_err(dev, "failed to get button release irq\n"); return irq; + } ret = devm_request_threaded_irq(dev, irq, NULL, mbhc_btn_release_irq_handler, diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index 4cbef9affffd..6979b3bc254c 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -22,6 +22,7 @@ #include "pcm512x.h" +#define PCM512x_MAX_NUM_SCLK 2 #define PCM512x_NUM_SUPPLIES 3 static const char * const pcm512x_supply_names[PCM512x_NUM_SUPPLIES] = { "AVDD", @@ -31,7 +32,7 @@ static const char * const pcm512x_supply_names[PCM512x_NUM_SUPPLIES] = { struct pcm512x_priv { struct regmap *regmap; - struct clk *sclk; + struct clk *sclk[PCM512x_MAX_NUM_SCLK]; struct regulator_bulk_data supplies[PCM512x_NUM_SUPPLIES]; struct notifier_block supply_nb[PCM512x_NUM_SUPPLIES]; int fmt; @@ -48,6 +49,8 @@ struct pcm512x_priv { int mute; struct mutex mutex; unsigned int bclk_ratio; + int num_clocks; + int sclk_src; }; /* @@ -584,11 +587,12 @@ static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream, struct device *dev = dai->dev; struct snd_pcm_hw_constraint_ratnums *constraints_no_pll; struct snd_ratnum *rats_no_pll; + int ret; - if (IS_ERR(pcm512x->sclk)) { + if (IS_ERR(pcm512x->sclk[0])) { dev_err(dev, "Need SCLK for master mode: %ld\n", - PTR_ERR(pcm512x->sclk)); - return PTR_ERR(pcm512x->sclk); + PTR_ERR(pcm512x->sclk[0])); + return PTR_ERR(pcm512x->sclk[0]); } if (pcm512x->pll_out) @@ -604,18 +608,39 @@ static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream, if (!constraints_no_pll) return -ENOMEM; constraints_no_pll->nrats = 1; + if (!IS_ERR(pcm512x->sclk[1])) + constraints_no_pll->nrats = 2; + rats_no_pll = devm_kzalloc(dev, sizeof(*rats_no_pll), GFP_KERNEL); if (!rats_no_pll) return -ENOMEM; constraints_no_pll->rats = rats_no_pll; - rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->num = clk_get_rate(pcm512x->sclk[0]) / 64; rats_no_pll->den_min = 1; rats_no_pll->den_max = 128; rats_no_pll->den_step = 1; - return snd_pcm_hw_constraint_ratnums(substream->runtime, 0, + ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, constraints_no_pll); + if (ret) { + dev_err(dev, "Failed to set ratnums constraints\n"); + return ret; + } + + if (!IS_ERR(pcm512x->sclk[1])) { + rats_no_pll->num = clk_get_rate(pcm512x->sclk[1]) / 64; + ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + constraints_no_pll); + if (ret) { + dev_err(dev, "Failed to set ratnums constraints\n"); + return ret; + } + } + + return 0; + } static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream, @@ -626,9 +651,9 @@ static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream, struct device *dev = dai->dev; struct regmap *regmap = pcm512x->regmap; - if (IS_ERR(pcm512x->sclk)) { + if (IS_ERR(pcm512x->sclk[pcm512x->sclk_src])) { dev_info(dev, "No SCLK, using BCLK: %ld\n", - PTR_ERR(pcm512x->sclk)); + PTR_ERR(pcm512x->sclk[pcm512x->sclk_src])); /* Disable reporting of missing SCLK as an error */ regmap_update_bits(regmap, PCM512x_ERROR_DETECT, @@ -920,7 +945,7 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai, } if (!pcm512x->pll_out) { - sck_rate = clk_get_rate(pcm512x->sclk); + sck_rate = clk_get_rate(pcm512x->sclk[pcm512x->sclk_src]); bclk_rate = params_rate(params) * lrclk_div; bclk_div = DIV_ROUND_CLOSEST(sck_rate, bclk_rate); @@ -937,7 +962,7 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai, } bclk_rate = ret; - pllin_rate = clk_get_rate(pcm512x->sclk); + pllin_rate = clk_get_rate(pcm512x->sclk[pcm512x->sclk_src]); sck_rate = pcm512x_find_sck(dai, bclk_rate); if (!sck_rate) @@ -1394,6 +1419,29 @@ static int pcm512x_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) return 0; } +static int pcm512x_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + if (dir == SND_SOC_CLOCK_IN) { + switch (clk_id) { + case PCM512x_SYSCLK_MCLK1: + case PCM512x_SYSCLK_MCLK2: + pcm512x->sclk_src = clk_id; + break; + default: + return -EINVAL; + } + + dev_dbg(component->dev, + "SCLK dir: %d; sclk_src: %d\n", dir, clk_id); + } + + return 0; +} + static int pcm512x_digital_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_component *component = dai->component; @@ -1447,6 +1495,7 @@ static const struct snd_soc_dai_ops pcm512x_dai_ops = { .set_fmt = pcm512x_set_fmt, .digital_mute = pcm512x_digital_mute, .set_bclk_ratio = pcm512x_set_bclk_ratio, + .set_sysclk = pcm512x_set_sysclk, }; static struct snd_soc_dai_driver pcm512x_dai = { @@ -1506,6 +1555,7 @@ EXPORT_SYMBOL_GPL(pcm512x_regmap); int pcm512x_probe(struct device *dev, struct regmap *regmap) { struct pcm512x_priv *pcm512x; + char clk_name[8]; int i, ret; pcm512x = devm_kzalloc(dev, sizeof(struct pcm512x_priv), GFP_KERNEL); @@ -1563,13 +1613,18 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap) goto err; } - pcm512x->sclk = devm_clk_get(dev, NULL); - if (PTR_ERR(pcm512x->sclk) == -EPROBE_DEFER) { + /* default to first sclk */ + pcm512x->num_clocks = 1; + pcm512x->sclk_src = PCM512x_SYSCLK_MCLK1; + + pcm512x->sclk[0] = devm_clk_get(dev, NULL); + if (PTR_ERR(pcm512x->sclk[0]) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; goto err; } - if (!IS_ERR(pcm512x->sclk)) { - ret = clk_prepare_enable(pcm512x->sclk); + + if (!IS_ERR(pcm512x->sclk[0])) { + ret = clk_prepare_enable(pcm512x->sclk[0]); if (ret != 0) { dev_err(dev, "Failed to enable SCLK: %d\n", ret); goto err; @@ -1594,6 +1649,26 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap) const struct device_node *np = dev->of_node; u32 val; + if (of_property_read_bool(np, "clocks")) { + pcm512x->num_clocks = + of_property_count_u32_elems(np, "clocks"); + if (pcm512x->num_clocks > PCM512x_MAX_NUM_SCLK) { + dev_err(dev, "Failed unsupported max sclk: %d\n", + pcm512x->num_clocks); + goto err; + } + + for (i = 0; i < pcm512x->num_clocks; i++) { + sprintf(clk_name, "sclk%d", i); + pcm512x->sclk[i] = devm_clk_get(dev, clk_name); + + if (IS_ERR(pcm512x->sclk[i])) { + dev_info(dev, "Failed to get sclk%d\n", i); + pcm512x->sclk[i] = NULL; + } + } + } + if (of_property_read_u32(np, "pll-in", &val) >= 0) { if (val > 6) { dev_err(dev, "Invalid pll-in\n"); @@ -1638,8 +1713,10 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap) err_pm: pm_runtime_disable(dev); err_clk: - if (!IS_ERR(pcm512x->sclk)) - clk_disable_unprepare(pcm512x->sclk); + for (i = 0; i < pcm512x->num_clocks; i++) { + if (!IS_ERR(pcm512x->sclk[i])) + clk_disable_unprepare(pcm512x->sclk[i]); + } err: regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies), pcm512x->supplies); @@ -1650,10 +1727,13 @@ EXPORT_SYMBOL_GPL(pcm512x_probe); void pcm512x_remove(struct device *dev) { struct pcm512x_priv *pcm512x = dev_get_drvdata(dev); + int i; pm_runtime_disable(dev); - if (!IS_ERR(pcm512x->sclk)) - clk_disable_unprepare(pcm512x->sclk); + for (i = 0; i < pcm512x->num_clocks; i++) { + if (!IS_ERR(pcm512x->sclk[i])) + clk_disable_unprepare(pcm512x->sclk[i]); + } regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies), pcm512x->supplies); } @@ -1663,7 +1743,7 @@ EXPORT_SYMBOL_GPL(pcm512x_remove); static int pcm512x_suspend(struct device *dev) { struct pcm512x_priv *pcm512x = dev_get_drvdata(dev); - int ret; + int ret, i; ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER, PCM512x_RQPD, PCM512x_RQPD); @@ -1679,8 +1759,10 @@ static int pcm512x_suspend(struct device *dev) return ret; } - if (!IS_ERR(pcm512x->sclk)) - clk_disable_unprepare(pcm512x->sclk); + for (i = 0; i < pcm512x->num_clocks; i++) { + if (!IS_ERR(pcm512x->sclk[i])) + clk_disable_unprepare(pcm512x->sclk[i]); + } return 0; } @@ -1688,13 +1770,15 @@ static int pcm512x_suspend(struct device *dev) static int pcm512x_resume(struct device *dev) { struct pcm512x_priv *pcm512x = dev_get_drvdata(dev); - int ret; - - if (!IS_ERR(pcm512x->sclk)) { - ret = clk_prepare_enable(pcm512x->sclk); - if (ret != 0) { - dev_err(dev, "Failed to enable SCLK: %d\n", ret); - return ret; + int ret, i; + + for (i = 0; i < pcm512x->num_clocks; i++) { + if (!IS_ERR(pcm512x->sclk[i])) { + ret = clk_prepare_enable(pcm512x->sclk[i]); + if (ret != 0) { + dev_err(dev, "Failed to enable SCLK: %d\n", ret); + return ret; + } } } diff --git a/sound/soc/codecs/pcm512x.h b/sound/soc/codecs/pcm512x.h index 08d04f539805..01b94eccbf15 100644 --- a/sound/soc/codecs/pcm512x.h +++ b/sound/soc/codecs/pcm512x.h @@ -255,6 +255,12 @@ #define PCM512x_AGBR_SHIFT 0 #define PCM512x_AGBL_SHIFT 4 +enum pcm512x_sclk_src { + PCM512x_SYSCLK_MCLK1, + PCM512x_SYSCLK_MCLK2, + PCM512x_SYSCLK_NOCLK, +}; + extern const struct dev_pm_ops pcm512x_pm_ops; extern const struct regmap_config pcm512x_regmap; diff --git a/sound/soc/codecs/rpmsg_ak4497.c b/sound/soc/codecs/rpmsg_ak4497.c new file mode 100644 index 000000000000..8f5657c57fbc --- /dev/null +++ b/sound/soc/codecs/rpmsg_ak4497.c @@ -0,0 +1,1110 @@ +/* + * ak4497.c -- audio driver for AK4497 + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Copyright (C) 2017, NXP + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/pcm_params.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <sound/pcm_params.h> +#include <linux/pm_runtime.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include "../fsl/fsl_rpmsg_i2s.h" + +#include "rpmsg_ak4497.h" + +//#define AK4497_DEBUG //used at debug mode +#define AK4497_NUM_SUPPLIES 2 +static const char *ak4497_supply_names[AK4497_NUM_SUPPLIES] = { + "DVDD", + "AVDD", +}; + +/* AK4497 Codec Private Data */ +struct rpmsg_ak4497_priv { + struct regmap *regmap; + int fs1; /* Sampling Frequency */ + int nBickFreq; /* 0: 48fs for 24bit, 1: 64fs or more for 32bit */ + int nTdmSds; + int pdn_gpio; + int mute_gpio; + int fmt; + struct regulator_bulk_data supplies[AK4497_NUM_SUPPLIES]; + struct fsl_rpmsg_i2s *rpmsg_i2s; + int audioindex; + struct platform_device *pdev; +}; + +/* ak4497 register cache & default register settings */ +static const struct reg_default ak4497_reg[] = { + { AK4497_00_CONTROL1, 0x0C}, + { AK4497_01_CONTROL2, 0x22}, + { AK4497_02_CONTROL3, 0x00}, + { AK4497_03_LCHATT, 0xFF}, + { AK4497_04_RCHATT, 0xFF}, + { AK4497_05_CONTROL4, 0x00}, + { AK4497_06_DSD1, 0x00}, + { AK4497_07_CONTROL5, 0x00}, + { AK4497_08_SOUNDCONTROL, 0x00}, + { AK4497_09_DSD2, 0x00}, + { AK4497_0A_CONTROL7, 0x04}, + { AK4497_0B_CONTROL8, 0x00}, + { AK4497_0C_RESERVED, 0x00}, + { AK4497_0D_RESERVED, 0x00}, + { AK4497_0E_RESERVED, 0x00}, + { AK4497_0F_RESERVED, 0x00}, + { AK4497_10_RESERVED, 0x00}, + { AK4497_11_RESERVED, 0x00}, + { AK4497_12_RESERVED, 0x00}, + { AK4497_13_RESERVED, 0x00}, + { AK4497_14_RESERVED, 0x00}, + { AK4497_15_DFSREAD, 0x00}, +}; + +/* Volume control: + * from -127 to 0 dB in 0.5 dB steps (mute instead of -127.5 dB) + */ +static DECLARE_TLV_DB_SCALE(latt_tlv, -12750, 50, 0); +static DECLARE_TLV_DB_SCALE(ratt_tlv, -12750, 50, 0); + +static const char * const ak4497_ecs_select_texts[] = {"768kHz", "384kHz"}; + +static const char * const ak4497_dem_select_texts[] = { + "44.1kHz", "OFF", "48kHz", "32kHz"}; +static const char * const ak4497_dzfm_select_texts[] = { + "Separated", "ANDed"}; + +static const char * const ak4497_sellr_select_texts[] = { + "Rch", "Lch"}; +static const char * const ak4497_dckb_select_texts[] = { + "Falling", "Rising"}; +static const char * const ak4497_dcks_select_texts[] = { + "512fs", "768fs"}; + +static const char * const ak4497_dsdd_select_texts[] = { + "Normal", "Volume Bypass"}; + +static const char * const ak4497_sc_select_texts[] = { + "Setting 1", "Setting 2", "Setting 3"}; +static const char * const ak4497_dsdf_select_texts[] = { + "50kHz", "150kHz"}; +static const char * const ak4497_dsd_input_path_select[] = { + "16_17_19pin", "3_4_5pin"}; +static const char * const ak4497_ats_select_texts[] = { + "4080/fs", "2040/fs", "510/fs", "255/fs"}; + +static const struct soc_enum ak4497_dac_enum[] = { + SOC_ENUM_SINGLE(AK4497_00_CONTROL1, 5, + ARRAY_SIZE(ak4497_ecs_select_texts), + ak4497_ecs_select_texts), + SOC_ENUM_SINGLE(AK4497_01_CONTROL2, 1, + ARRAY_SIZE(ak4497_dem_select_texts), + ak4497_dem_select_texts), + SOC_ENUM_SINGLE(AK4497_01_CONTROL2, 6, + ARRAY_SIZE(ak4497_dzfm_select_texts), + ak4497_dzfm_select_texts), + SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 1, + ARRAY_SIZE(ak4497_sellr_select_texts), + ak4497_sellr_select_texts), + SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 4, + ARRAY_SIZE(ak4497_dckb_select_texts), + ak4497_dckb_select_texts), + SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 5, + ARRAY_SIZE(ak4497_dcks_select_texts), + ak4497_dcks_select_texts), + SOC_ENUM_SINGLE(AK4497_06_DSD1, 1, + ARRAY_SIZE(ak4497_dsdd_select_texts), + ak4497_dsdd_select_texts), + SOC_ENUM_SINGLE(AK4497_08_SOUNDCONTROL, 0, + ARRAY_SIZE(ak4497_sc_select_texts), + ak4497_sc_select_texts), + SOC_ENUM_SINGLE(AK4497_09_DSD2, 1, + ARRAY_SIZE(ak4497_dsdf_select_texts), + ak4497_dsdf_select_texts), + SOC_ENUM_SINGLE(AK4497_09_DSD2, 2, + ARRAY_SIZE(ak4497_dsd_input_path_select), + ak4497_dsd_input_path_select), + SOC_ENUM_SINGLE(AK4497_0B_CONTROL8, 6, + ARRAY_SIZE(ak4497_ats_select_texts), + ak4497_ats_select_texts), +}; + +static const char * const ak4497_dsdsel_select_texts[] = { + "64fs", "128fs", "256fs", "512fs"}; +static const char * const ak4497_bickfreq_select[] = {"48fs", "64fs"}; + +static const char * const ak4497_tdm_sds_select[] = { + "L1R1", "TDM128_L1R1", "TDM128_L2R2", + "TDM256_L1R1", "TDM256_L2R2", "TDM256_L3R3", "TDM256_L4R4", + "TDM512_L1R1", "TDM512_L2R2", "TDM512_L3R3", "TDM512_L4R4", + "TDM512_L5R5", "TDM512_L6R6", "TDM512_L7R7", "TDM512_L8R8", +}; + +static const char * const ak4497_adfs_select[] = { + "Normal Speed Mode", "Double Speed Mode", "Quad Speed Mode", + "Quad Speed Mode", "Oct Speed Mode", "Hex Speed Mode", "Oct Speed Mode", + "Hex Speed Mode" +}; + +static const struct soc_enum ak4497_dac_enum2[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_dsdsel_select_texts), + ak4497_dsdsel_select_texts), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_bickfreq_select), + ak4497_bickfreq_select), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_tdm_sds_select), + ak4497_tdm_sds_select), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_adfs_select), + ak4497_adfs_select) +}; + +static int ak4497_read(struct snd_soc_component *component, unsigned int reg, + unsigned int *val) +{ + int ret; + + ret = snd_soc_component_read(component, reg, val); + if (ret < 0) + dev_err(component->dev, "Register %u read failed, ret=%d.\n", reg, ret); + + return ret; +} + +static int ak4497_get_dsdsel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int dsdsel0, dsdsel1; + + ak4497_read(component, AK4497_06_DSD1, &dsdsel0); + dsdsel0 &= AK4497_DSDSEL0; + + ak4497_read(component, AK4497_09_DSD2, &dsdsel1); + dsdsel1 &= AK4497_DSDSEL1; + + ucontrol->value.enumerated.item[0] = ((dsdsel1 << 1) | dsdsel0); + + return 0; +} + +static int ak4497_set_dsdsel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int dsdsel = ucontrol->value.enumerated.item[0]; + + switch (dsdsel) { + case 0: /* 2.8224MHz */ + snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x00); + snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x00); + break; + case 1: /* 5.6448MHz */ + snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x01); + snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x00); + break; + case 2: /* 11.2896MHz */ + snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x00); + snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x01); + break; + case 3: /* 22.5792MHz */ + snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x01); + snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x01); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ak4497_get_bickfs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = ak4497->nBickFreq; + + return 0; +} + +static int ak4497_set_bickfs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + + ak4497->nBickFreq = ucontrol->value.enumerated.item[0]; + + return 0; +} + +static int ak4497_get_tdmsds(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = ak4497->nTdmSds; + + return 0; +} + +static int ak4497_set_tdmsds(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + int regA, regB; + + ak4497->nTdmSds = ucontrol->value.enumerated.item[0]; + + if (ak4497->nTdmSds == 0) + regB = 0; /* SDS0 bit = 0 */ + else + regB = (1 & (ak4497->nTdmSds - 1)); /* SDS0 bit = 1 */ + + switch (ak4497->nTdmSds) { + case 0: + regA = 0; /* Normal */ + break; + case 1: + case 2: + regA = 4; /* TDM128 TDM1-0bits = 1 */ + break; + case 3: + case 4: + regA = 8; /* TDM128 TDM1-0bits = 2 */ + break; + case 5: + case 6: + regA = 9; /* TDM128 TDM1-0bits = 2 */ + break; + case 7: + case 8: + regA = 0xC; /* TDM128 TDM1-0bits = 3 */ + break; + case 9: + case 10: + regA = 0xD; /* TDM128 TDM1-0bits = 3 */ + break; + case 11: + case 12: + regA = 0xE; /* TDM128 TDM1-0bits = 3 */ + break; + case 13: + case 14: + regA = 0xF; /* TDM128 TDM1-0bits = 3 */ + break; + default: + regA = 0; + regB = 0; + break; + } + + regA <<= 4; + regB <<= 4; + + snd_soc_component_update_bits(component, AK4497_0A_CONTROL7, 0xF0, regA); + snd_soc_component_update_bits(component, AK4497_0B_CONTROL8, 0x10, regB); + + return 0; +} + +static int ak4497_get_adfs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int nADFSbit; + + ak4497_read(component, AK4497_15_DFSREAD, &nADFSbit); + nADFSbit &= 0x7; + + ucontrol->value.enumerated.item[0] = nADFSbit; + + return 0; +} + +static int ak4497_set_adfs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("AK4497 : ADFS is read only\n"); + + return 0; +} + +static const char * const gain_control_texts[] = { + "2.8_2.8Vpp", "2.8_2.5Vpp", "2.5_2.5Vpp", "3.75_3.75Vpp", "3.75_2.5Vpp" +}; + +static const unsigned int gain_control_values[] = { + 0, 1, 2, 4, 5 +}; + +static const struct soc_enum ak4497_gain_control_enum = + SOC_VALUE_ENUM_SINGLE(AK4497_07_CONTROL5, 1, 7, + ARRAY_SIZE(gain_control_texts), + gain_control_texts, + gain_control_values); + +#ifdef AK4497_DEBUG + +static const char * const test_reg_select[] = { + "read AK4497 Reg 00:0B", + "read AK4497 Reg 15" +}; + +static const struct soc_enum ak4497_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(test_reg_select), test_reg_select), +}; + +static int nTestRegNo; + +static int get_test_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* Get the current output routing */ + ucontrol->value.enumerated.item[0] = nTestRegNo; + + return 0; +} + +static int set_test_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + u32 currMode = ucontrol->value.enumerated.item[0]; + int i, regs, rege; + unsigned int value; + + nTestRegNo = currMode; + + if (nTestRegNo == 0) { + regs = 0x00; + rege = 0x0B; + } else { + regs = 0x15; + rege = 0x15; + } + + for (i = regs; i <= rege; i++) { + ak4497_read(component, i, &value); + pr_debug("***AK4497 Addr,Reg=(%x, %x)\n", i, value); + } + + return 0; +} +#endif + +static const struct snd_kcontrol_new ak4497_snd_controls[] = { + SOC_SINGLE_TLV("AK4497 Lch Digital Volume", + AK4497_03_LCHATT, 0, 0xFF, 0, latt_tlv), + SOC_SINGLE_TLV("AK4497 Rch Digital Volume", + AK4497_04_RCHATT, 0, 0xFF, 0, ratt_tlv), + + SOC_ENUM("AK4497 EX DF I/F clock", ak4497_dac_enum[0]), + SOC_ENUM("AK4497 De-emphasis Response", ak4497_dac_enum[1]), + SOC_ENUM("AK4497 Data Zero Detect Mode", ak4497_dac_enum[2]), + SOC_ENUM("AK4497 Data Selection at Mono Mode", ak4497_dac_enum[3]), + + SOC_ENUM("AK4497 Polarity of DCLK", ak4497_dac_enum[4]), + SOC_ENUM("AK4497 DCKL Frequency", ak4497_dac_enum[5]), + + SOC_ENUM("AK4497 DDSD Play Back Path", ak4497_dac_enum[6]), + SOC_ENUM("AK4497 Sound control", ak4497_dac_enum[7]), + SOC_ENUM("AK4497 Cut Off of DSD Filter", ak4497_dac_enum[8]), + + SOC_ENUM_EXT("AK4497 DSD Data Stream", ak4497_dac_enum2[0], + ak4497_get_dsdsel, ak4497_set_dsdsel), + SOC_ENUM_EXT("AK4497 BICK Frequency Select", ak4497_dac_enum2[1], + ak4497_get_bickfs, ak4497_set_bickfs), + SOC_ENUM_EXT("AK4497 TDM Data Select", ak4497_dac_enum2[2], + ak4497_get_tdmsds, ak4497_set_tdmsds), + + SOC_SINGLE("AK4497 External Digital Filter", AK4497_00_CONTROL1, + 6, 1, 0), + SOC_SINGLE("AK4497 MCLK Frequency Auto Setting", AK4497_00_CONTROL1, + 7, 1, 0), + SOC_SINGLE("AK4497 MCLK FS Auto Detect", AK4497_00_CONTROL1, 4, 1, 0), + + SOC_SINGLE("AK4497 Soft Mute Control", AK4497_01_CONTROL2, 0, 1, 0), + SOC_SINGLE("AK4497 Short delay filter", AK4497_01_CONTROL2, 5, 1, 0), + SOC_SINGLE("AK4497 Data Zero Detect Enable", AK4497_01_CONTROL2, + 7, 1, 0), + SOC_SINGLE("AK4497 Slow Roll-off Filter", AK4497_02_CONTROL3, 0, 1, 0), + SOC_SINGLE("AK4497 Invering Enable of DZF", AK4497_02_CONTROL3, + 4, 1, 0), + SOC_SINGLE("AK4497 Mono Mode", AK4497_02_CONTROL3, 3, 1, 0), + SOC_SINGLE("AK4497 Super Slow Roll-off Filter", AK4497_05_CONTROL4, + 0, 1, 0), + SOC_SINGLE("AK4497 AOUTR Phase Inverting", AK4497_05_CONTROL4, + 6, 1, 0), + SOC_SINGLE("AK4497 AOUTL Phase Inverting", AK4497_05_CONTROL4, + 7, 1, 0), + SOC_SINGLE("AK4497 DSD Mute Release", AK4497_06_DSD1, 3, 1, 0), + SOC_SINGLE("AK4497 DSD Mute Control Hold", AK4497_06_DSD1, 4, 1, 0), + SOC_SINGLE("AK4497 DSDR is detected", AK4497_06_DSD1, 5, 1, 0), + SOC_SINGLE("AK4497 DSDL is detected", AK4497_06_DSD1, 6, 1, 0), + SOC_SINGLE("AK4497 DSD Data Mute", AK4497_06_DSD1, 7, 1, 0), + SOC_SINGLE("AK4497 Synchronization Control", AK4497_07_CONTROL5, + 0, 1, 0), + + SOC_ENUM("AK4497 Output Level", ak4497_gain_control_enum), + SOC_SINGLE("AK4497 High Sonud Quality Mode", AK4497_08_SOUNDCONTROL, + 2, 1, 0), + SOC_SINGLE("AK4497 Heavy Load Mode", AK4497_08_SOUNDCONTROL, 3, 1, 0), + SOC_ENUM("AK4497 DSD Data Input Pin", ak4497_dac_enum[9]), + SOC_SINGLE("AK4497 Daisy Chain", AK4497_0B_CONTROL8, 1, 1, 0), + SOC_ENUM("AK4497 ATT Transit Time", ak4497_dac_enum[10]), + + SOC_ENUM_EXT("AK4497 Read FS Auto Detect Mode", ak4497_dac_enum2[3], + ak4497_get_adfs, ak4497_set_adfs), + +#ifdef AK4497_DEBUG + SOC_ENUM_EXT("Reg Read", ak4497_enum[0], get_test_reg, set_test_reg), +#endif + +}; + +static const char * const ak4497_dac_enable_texts[] = {"Off", "On"}; + +static SOC_ENUM_SINGLE_VIRT_DECL(ak4497_dac_enable_enum, + ak4497_dac_enable_texts); + +static const struct snd_kcontrol_new ak4497_dac_enable_control = + SOC_DAPM_ENUM("DAC Switch", ak4497_dac_enable_enum); + +/* ak4497 dapm widgets */ +static const struct snd_soc_dapm_widget ak4497_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("AK4497 SDTI", "Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("AK4497 DAC", NULL, AK4497_0A_CONTROL7, 2, 0), + + SND_SOC_DAPM_MUX("AK4497 DAC Enable", SND_SOC_NOPM, + 0, 0, &ak4497_dac_enable_control), + + SND_SOC_DAPM_OUTPUT("AK4497 AOUT"), + +}; + +static const struct snd_soc_dapm_route ak4497_intercon[] = { + {"AK4497 DAC", NULL, "AK4497 SDTI"}, + {"AK4497 DAC Enable", "On", "AK4497 DAC"}, + {"AK4497 AOUT", NULL, "AK4497 DAC Enable"}, +}; + +static int ak4497_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + snd_pcm_format_t pcm_format = params_format(params); + + unsigned int dfs, dfs2, dsdsel0, dsdsel1, format; + int nfs1; + bool is_dsd = false; + int dsd_bclk; + + if (pcm_format == SNDRV_PCM_FORMAT_DSD_U8 || + pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE || + pcm_format == SNDRV_PCM_FORMAT_DSD_U16_BE || + pcm_format == SNDRV_PCM_FORMAT_DSD_U32_LE || + pcm_format == SNDRV_PCM_FORMAT_DSD_U32_BE) + is_dsd = true; + + nfs1 = params_rate(params); + ak4497->fs1 = nfs1; + + ak4497_read(component, AK4497_01_CONTROL2, &dfs); + dfs &= ~AK4497_DFS; + + ak4497_read(component, AK4497_05_CONTROL4, &dfs2); + dfs2 &= ~AK4497_DFS2; + + ak4497_read(component, AK4497_06_DSD1, &dsdsel0); + dsdsel0 &= ~AK4497_DSDSEL0; + + ak4497_read(component, AK4497_09_DSD2, &dsdsel1); + dsdsel1 &= ~AK4497_DSDSEL1; + + if (!is_dsd) { + switch (nfs1) { + case 8000: + case 11025: + case 16000: + case 22050: + case 32000: + case 44100: + case 48000: + dfs |= AK4497_DFS_48KHZ; + dfs2 |= AK4497_DFS2_48KHZ; + break; + case 88200: + case 96000: + dfs |= AK4497_DFS_96KHZ; + dfs2 |= AK4497_DFS2_48KHZ; + break; + case 176400: + case 192000: + dfs |= AK4497_DFS_192KHZ; + dfs2 |= AK4497_DFS2_48KHZ; + break; + case 352800: + case 384000: + dfs |= AK4497_DFS_384KHZ; + dfs2 |= AK4497_DFS2_384KHZ; + break; + case 705600: + case 768000: + dfs |= AK4497_DFS_768KHZ; + dfs2 |= AK4497_DFS2_384KHZ; + break; + default: + return -EINVAL; + } + } else { + dsd_bclk = params_rate(params) * + params_physical_width(params); + + switch (dsd_bclk) { + case 2822400: + dsdsel0 |= AK4497_DSDSEL0_2MHZ; + dsdsel1 |= AK4497_DSDSEL1_2MHZ; + break; + case 5644800: + dsdsel0 |= AK4497_DSDSEL0_5MHZ; + dsdsel1 |= AK4497_DSDSEL1_5MHZ; + break; + case 11289600: + dsdsel0 |= AK4497_DSDSEL0_11MHZ; + dsdsel1 |= AK4497_DSDSEL1_11MHZ; + break; + case 22579200: + dsdsel0 |= AK4497_DSDSEL0_22MHZ; + dsdsel1 |= AK4497_DSDSEL1_22MHZ; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, AK4497_06_DSD1, dsdsel0); + snd_soc_component_write(component, AK4497_09_DSD2, dsdsel1); + } + + snd_soc_component_write(component, AK4497_01_CONTROL2, dfs); + snd_soc_component_write(component, AK4497_05_CONTROL4, dfs2); + + ak4497_read(component, AK4497_00_CONTROL1, &format); + format &= ~AK4497_DIF; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (ak4497->fmt == SND_SOC_DAIFMT_I2S) + format |= AK4497_DIF_24BIT_I2S; + else + format |= AK4497_DIF_16BIT_LSB; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + if (ak4497->fmt == SND_SOC_DAIFMT_I2S) + format |= AK4497_DIF_32BIT_I2S; + else if (ak4497->fmt == SND_SOC_DAIFMT_LEFT_J) + format |= AK4497_DIF_32BIT_MSB; + else if (ak4497->fmt == SND_SOC_DAIFMT_RIGHT_J) + format |= AK4497_DIF_32BIT_LSB; + else + return -EINVAL; + break; + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, AK4497_00_CONTROL1, format); + + return 0; +} + +static int ak4497_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + return 0; +} + +static int ak4497_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + unsigned int format, format2; + + /* set master/slave audio interface */ + ak4497_read(component, AK4497_00_CONTROL1, &format); + format &= ~AK4497_DIF; + + ak4497_read(component, AK4497_02_CONTROL3, &format2); + format2 &= ~AK4497_DIF_DSD; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "Clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ak4497->fmt = SND_SOC_DAIFMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ak4497->fmt = SND_SOC_DAIFMT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ak4497->fmt = SND_SOC_DAIFMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_PDM: + format2 |= AK4497_DIF_DSD_MODE; + break; + default: + return -EINVAL; + } + + /* set format */ + snd_soc_component_write(component, AK4497_00_CONTROL1, format); + snd_soc_component_write(component, AK4497_02_CONTROL3, format2); + + return 0; +} + +static bool ak4497_volatile(struct device *dev, unsigned int reg) +{ + int ret; + +#ifdef AK4497_DEBUG + ret = 1; +#else + switch (reg) { + case AK4497_15_DFSREAD: + ret = 1; + break; + default: + ret = 0; + break; + } +#endif + return ret; +} + +static int ak4497_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + /* RSTN bit = 1 */ + snd_soc_component_update_bits(component, AK4497_00_CONTROL1, 0x01, 0x01); + break; + case SND_SOC_BIAS_OFF: + /* RSTN bit = 0 */ + snd_soc_component_update_bits(component, AK4497_00_CONTROL1, 0x01, 0x00); + break; + } + + return 0; +} + +#define AK4497_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\ + SNDRV_PCM_RATE_192000) + +#define AK4497_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE |\ + SNDRV_PCM_FMTBIT_DSD_U8 |\ + SNDRV_PCM_FMTBIT_DSD_U16_LE |\ + SNDRV_PCM_FMTBIT_DSD_U32_LE) + +static const unsigned int ak4497_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, + 2822400, +}; + +static const struct snd_pcm_hw_constraint_list ak4497_rate_constraints = { + .count = ARRAY_SIZE(ak4497_rates), + .list = ak4497_rates, +}; + +static int ak4497_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &ak4497_rate_constraints); + + return ret; +} + +static struct snd_soc_dai_ops ak4497_dai_ops = { + .startup = ak4497_startup, + .hw_params = ak4497_hw_params, + .set_sysclk = ak4497_set_dai_sysclk, + .set_fmt = ak4497_set_dai_fmt, +}; + +struct snd_soc_dai_driver rpmsg_ak4497_dai[] = { + { + .name = "rpmsg-ak4497-aif", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = AK4497_FORMATS, + }, + .ops = &ak4497_dai_ops, + }, +}; + +static int ak4497_init_reg(struct snd_soc_component *component) +{ + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + int ret = 0; + + /* External Mute ON */ + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_set_value_cansleep(ak4497->mute_gpio, 1); + + if (gpio_is_valid(ak4497->pdn_gpio)) { + gpio_set_value_cansleep(ak4497->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(ak4497->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + /* ak4497_set_bias_level(codec, SND_SOC_BIAS_STANDBY); */ + + /* SYNCE bit = 1 */ + ret = snd_soc_component_update_bits(component, AK4497_07_CONTROL5, 0x01, 0x01); + if (ret) + return ret; + + /* HLOAD bit = 1, SC2 bit = 1 */ + ret = snd_soc_component_update_bits(component, AK4497_08_SOUNDCONTROL, 0x0F, 0x0C); + if (ret) + return ret; + + return ret; +} + +static int ak4497_parse_dt(struct rpmsg_ak4497_priv *ak4497) +{ + struct device *dev; + struct device_node *np; + + dev = &(ak4497->pdev->dev); + np = dev->of_node; + + ak4497->pdn_gpio = -1; + ak4497->mute_gpio = -1; + + if (!np) + return 0; + + ak4497->pdn_gpio = of_get_named_gpio(np, "ak4497,pdn-gpio", 0); + if (ak4497->pdn_gpio < 0) + ak4497->pdn_gpio = -1; + + if (!gpio_is_valid(ak4497->pdn_gpio)) { + dev_err(dev, "ak4497 pdn pin(%u) is invalid\n", + ak4497->pdn_gpio); + ak4497->pdn_gpio = -1; + } + + ak4497->mute_gpio = of_get_named_gpio(np, "ak4497,mute-gpio", 0); + if (ak4497->mute_gpio < 0) + ak4497->mute_gpio = -1; + + if (!gpio_is_valid(ak4497->mute_gpio)) { + dev_err(dev, "ak4497 mute_gpio(%u) is invalid\n", + ak4497->mute_gpio); + ak4497->mute_gpio = -1; + } + + return 0; +} + +static int ak4497_probe(struct snd_soc_component *component) +{ + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + int ret = 0; + + ret = ak4497_parse_dt(ak4497); + if (ret) + return ret; + + if (gpio_is_valid(ak4497->pdn_gpio)) { + ret = gpio_request(ak4497->pdn_gpio, "ak4497 pdn"); + if (ret) + return ret; + gpio_direction_output(ak4497->pdn_gpio, 0); + } + if (gpio_is_valid(ak4497->mute_gpio)) { + ret = gpio_request(ak4497->mute_gpio, "ak4497 mute"); + if (ret) + return ret; + gpio_direction_output(ak4497->mute_gpio, 0); + } + + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "AK4497 AOUT"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "Playback"); + + ret = ak4497_init_reg(component); + if (ret) + return ret; + + ak4497->fs1 = 48000; + ak4497->nBickFreq = 1; + ak4497->nTdmSds = 0; + + return ret; +} + +static void ak4497_remove(struct snd_soc_component *component) +{ + struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component); + + ak4497_set_bias_level(component, SND_SOC_BIAS_OFF); + if (gpio_is_valid(ak4497->pdn_gpio)) { + gpio_set_value_cansleep(ak4497->pdn_gpio, 0); + gpio_free(ak4497->pdn_gpio); + } + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_free(ak4497->mute_gpio); + +} + +#ifdef CONFIG_PM +static int ak4497_runtime_suspend(struct device *dev) +{ + struct rpmsg_ak4497_priv *ak4497 = dev_get_drvdata(dev); + + regcache_cache_only(ak4497->regmap, true); + + if (gpio_is_valid(ak4497->pdn_gpio)) { + gpio_set_value_cansleep(ak4497->pdn_gpio, 0); + usleep_range(1000, 2000); + } + + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_free(ak4497->mute_gpio); + + return 0; +} + +static int ak4497_runtime_resume(struct device *dev) +{ + struct rpmsg_ak4497_priv *ak4497 = dev_get_drvdata(dev); + + /* External Mute ON */ + if (gpio_is_valid(ak4497->mute_gpio)) + gpio_set_value_cansleep(ak4497->mute_gpio, 1); + + if (gpio_is_valid(ak4497->pdn_gpio)) { + gpio_set_value_cansleep(ak4497->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + regcache_cache_only(ak4497->regmap, false); + regcache_mark_dirty(ak4497->regmap); + + return regcache_sync(ak4497->regmap); +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops ak4497_pm = { + SET_RUNTIME_PM_OPS(ak4497_runtime_suspend, ak4497_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +struct snd_soc_component_driver rpmsg_codec_dev_ak4497 = { + .probe = ak4497_probe, + .remove = ak4497_remove, + + .set_bias_level = ak4497_set_bias_level, + .controls = ak4497_snd_controls, + .num_controls = ARRAY_SIZE(ak4497_snd_controls), + .dapm_widgets = ak4497_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4497_dapm_widgets), + .dapm_routes = ak4497_intercon, + .num_dapm_routes = ARRAY_SIZE(ak4497_intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int rpmsg_ak4497_read(void *context, unsigned int reg, unsigned int *val) +{ + struct rpmsg_ak4497_priv *ak4497 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = ak4497->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg; + int err, reg_val; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = ak4497->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->header.cmd = GET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info); + reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data; + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + *val = reg_val; + return 0; +} + +static int rpmsg_ak4497_write(void *context, unsigned int reg, unsigned int val) +{ + struct rpmsg_ak4497_priv *ak4497 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = ak4497->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg; + int err; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = ak4497->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->param.buffer_size = val; + rpmsg->header.cmd = SET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info); + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + return 0; +} + +static const struct regmap_config rpmsg_ak4497_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4497_MAX_REGISTERS, + .volatile_reg = ak4497_volatile, + + .reg_defaults = ak4497_reg, + .num_reg_defaults = ARRAY_SIZE(ak4497_reg), + .cache_type = REGCACHE_RBTREE, + + .reg_read = rpmsg_ak4497_read, + .reg_write = rpmsg_ak4497_write, +}; + +static int rpmsg_ak4497_codec_probe(struct platform_device *pdev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent); + struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data; + struct rpmsg_ak4497_priv *ak4497; + int ret = 0; + int i; + + ak4497 = devm_kzalloc(&pdev->dev, + sizeof(struct rpmsg_ak4497_priv), GFP_KERNEL); + if (ak4497 == NULL) + return -ENOMEM; + + ak4497->rpmsg_i2s = rpmsg_i2s; + ak4497->pdev = pdev; + + ak4497->regmap = devm_regmap_init(&pdev->dev, NULL, ak4497, &rpmsg_ak4497_regmap); + if (IS_ERR(ak4497->regmap)) + return PTR_ERR(ak4497->regmap); + + if (pdata) + ak4497->audioindex = pdata->audioindex; + + dev_set_drvdata(&pdev->dev, ak4497); + + for (i = 0; i < ARRAY_SIZE(ak4497->supplies); i++) + ak4497->supplies[i].supply = ak4497_supply_names[i]; + + ret = devm_regulator_bulk_get(&pdev->dev, ARRAY_SIZE(ak4497->supplies), + ak4497->supplies); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ak4497->supplies), + ak4497->supplies); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &rpmsg_codec_dev_ak4497, + &rpmsg_ak4497_dai[0], ARRAY_SIZE(rpmsg_ak4497_dai)); + if (ret < 0) + return ret; + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int rpmsg_ak4497_codec_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static struct platform_driver rpmsg_ak4497_codec_driver = { + .driver = { + .name = RPMSG_CODEC_DRV_NAME_AK4497, + .pm = &ak4497_pm, + }, + .probe = rpmsg_ak4497_codec_probe, + .remove = rpmsg_ak4497_codec_remove, +}; + +module_platform_driver(rpmsg_ak4497_codec_driver); + +MODULE_AUTHOR("Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>"); +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>"); +MODULE_DESCRIPTION("ASoC ak4497 codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rpmsg_ak4497.h b/sound/soc/codecs/rpmsg_ak4497.h new file mode 100644 index 000000000000..33644088cc94 --- /dev/null +++ b/sound/soc/codecs/rpmsg_ak4497.h @@ -0,0 +1,90 @@ +/* + * ak4497.h -- audio driver for ak4497 + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Copyright (C) 2017, NXP + * + * 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. + * + */ +#ifndef _RPMSG_AK4497_H +#define _RPMSG_AK4497_H + +#define AK4497_00_CONTROL1 0x00 +#define AK4497_01_CONTROL2 0x01 +#define AK4497_02_CONTROL3 0x02 +#define AK4497_03_LCHATT 0x03 +#define AK4497_04_RCHATT 0x04 +#define AK4497_05_CONTROL4 0x05 +#define AK4497_06_DSD1 0x06 +#define AK4497_07_CONTROL5 0x07 +#define AK4497_08_SOUNDCONTROL 0x08 +#define AK4497_09_DSD2 0x09 +#define AK4497_0A_CONTROL7 0x0A +#define AK4497_0B_CONTROL8 0x0B +#define AK4497_0C_RESERVED 0x0C +#define AK4497_0D_RESERVED 0x0D +#define AK4497_0E_RESERVED 0x0E +#define AK4497_0F_RESERVED 0x0F +#define AK4497_10_RESERVED 0x10 +#define AK4497_11_RESERVED 0x11 +#define AK4497_12_RESERVED 0x12 +#define AK4497_13_RESERVED 0x13 +#define AK4497_14_RESERVED 0x14 +#define AK4497_15_DFSREAD 0x15 + + +#define AK4497_MAX_REGISTERS (AK4497_15_DFSREAD) + +/* Bitfield Definitions */ + +/* AK4497_00_CONTROL1 (0x00) Fields */ +#define AK4497_DIF 0x0E +#define AK4497_DIF_MSB_MODE (2 << 1) +#define AK4497_DIF_I2S_MODE (3 << 1) +#define AK4497_DIF_32BIT_MODE (4 << 1) + +#define AK4497_DIF_16BIT_LSB (0 << 1) +#define AK4497_DIF_20BIT_LSB (1 << 1) +#define AK4497_DIF_24BIT_MSB (2 << 1) +#define AK4497_DIF_24BIT_I2S (3 << 1) +#define AK4497_DIF_24BIT_LSB (4 << 1) +#define AK4497_DIF_32BIT_LSB (5 << 1) +#define AK4497_DIF_32BIT_MSB (6 << 1) +#define AK4497_DIF_32BIT_I2S (7 << 1) + +/* AK4497_02_CONTROL3 (0x02) Fields */ +#define AK4497_DIF_DSD 0x80 +#define AK4497_DIF_DSD_MODE (1 << 7) + + +/* AK4497_01_CONTROL2 (0x01) Fields */ +/* AK4497_05_CONTROL4 (0x05) Fields */ +#define AK4497_DFS 0x18 +#define AK4497_DFS_48KHZ (0x0 << 3) // 30kHz to 54kHz +#define AK4497_DFS_96KHZ (0x1 << 3) // 54kHz to 108kHz +#define AK4497_DFS_192KHZ (0x2 << 3) // 120kHz to 216kHz +#define AK4497_DFS_384KHZ (0x0 << 3) +#define AK4497_DFS_768KHZ (0x1 << 3) + +#define AK4497_DFS2 0x2 +#define AK4497_DFS2_48KHZ (0x0 << 1) // 30kHz to 216kHz +#define AK4497_DFS2_384KHZ (0x1 << 1) // 384kHz, 768kHz to 108kHz + + +#define AK4497_DSDSEL0 0x1 +#define AK4497_DSDSEL0_2MHZ 0x0 +#define AK4497_DSDSEL0_5MHZ 0x1 +#define AK4497_DSDSEL0_11MHZ 0x0 +#define AK4497_DSDSEL0_22MHZ 0x1 + +#define AK4497_DSDSEL1 0x1 +#define AK4497_DSDSEL1_2MHZ 0x0 +#define AK4497_DSDSEL1_5MHZ 0x0 +#define AK4497_DSDSEL1_11MHZ 0x1 +#define AK4497_DSDSEL1_22MHZ 0x1 + +#endif diff --git a/sound/soc/codecs/rpmsg_cs42xx8.c b/sound/soc/codecs/rpmsg_cs42xx8.c new file mode 100644 index 000000000000..02bdfb0cfd49 --- /dev/null +++ b/sound/soc/codecs/rpmsg_cs42xx8.c @@ -0,0 +1,752 @@ +/* + * Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <Guangyu.Chen@freescale.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include "rpmsg_cs42xx8.h" +#include "../fsl/fsl_rpmsg_i2s.h" + +#define CS42XX8_NUM_SUPPLIES 4 +static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = { + "VA", + "VD", + "VLS", + "VLC", +}; + +#define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* codec private data */ +struct rpmsg_cs42xx8_priv { + struct regulator_bulk_data supplies[CS42XX8_NUM_SUPPLIES]; + struct cs42xx8_driver_data *drvdata; + struct regmap *regmap; + struct clk *clk; + + bool slave_mode[2]; + unsigned long sysclk; + u32 tx_channels; + int rate[2]; + int reset_gpio; + int audioindex; + struct fsl_rpmsg_i2s *rpmsg_i2s; +}; + +/* -127.5dB to 0dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +/* -64dB to 24dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 0); + +static const char *const cs42xx8_adc_single[] = { "Differential", "Single-Ended" }; +static const char *const cs42xx8_szc[] = { "Immediate Change", "Zero Cross", + "Soft Ramp", "Soft Ramp on Zero Cross" }; + +static const struct soc_enum adc1_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 4, 2, cs42xx8_adc_single); +static const struct soc_enum adc2_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 3, 2, cs42xx8_adc_single); +static const struct soc_enum adc3_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 2, 2, cs42xx8_adc_single); +static const struct soc_enum dac_szc_enum = + SOC_ENUM_SINGLE(CS42XX8_TXCTL, 5, 4, cs42xx8_szc); +static const struct soc_enum adc_szc_enum = + SOC_ENUM_SINGLE(CS42XX8_TXCTL, 0, 4, cs42xx8_szc); + +static const struct snd_kcontrol_new cs42xx8_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC1 Playback Volume", CS42XX8_VOLAOUT1, + CS42XX8_VOLAOUT2, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC2 Playback Volume", CS42XX8_VOLAOUT3, + CS42XX8_VOLAOUT4, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC3 Playback Volume", CS42XX8_VOLAOUT5, + CS42XX8_VOLAOUT6, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC4 Playback Volume", CS42XX8_VOLAOUT7, + CS42XX8_VOLAOUT8, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", CS42XX8_VOLAIN1, + CS42XX8_VOLAIN2, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", CS42XX8_VOLAIN3, + CS42XX8_VOLAIN4, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE("DAC1 Invert Switch", CS42XX8_DACINV, 0, 1, 1, 0), + SOC_DOUBLE("DAC2 Invert Switch", CS42XX8_DACINV, 2, 3, 1, 0), + SOC_DOUBLE("DAC3 Invert Switch", CS42XX8_DACINV, 4, 5, 1, 0), + SOC_DOUBLE("DAC4 Invert Switch", CS42XX8_DACINV, 6, 7, 1, 0), + SOC_DOUBLE("ADC1 Invert Switch", CS42XX8_ADCINV, 0, 1, 1, 0), + SOC_DOUBLE("ADC2 Invert Switch", CS42XX8_ADCINV, 2, 3, 1, 0), + SOC_SINGLE("ADC High-Pass Filter Switch", CS42XX8_ADCCTL, 7, 1, 1), + SOC_SINGLE("DAC De-emphasis Switch", CS42XX8_ADCCTL, 5, 1, 0), + SOC_ENUM("ADC1 Single Ended Mode Switch", adc1_single_enum), + SOC_ENUM("ADC2 Single Ended Mode Switch", adc2_single_enum), + SOC_SINGLE("DAC Single Volume Control Switch", CS42XX8_TXCTL, 7, 1, 0), + SOC_ENUM("DAC Soft Ramp & Zero Cross Control Switch", dac_szc_enum), + SOC_SINGLE("DAC Auto Mute Switch", CS42XX8_TXCTL, 4, 1, 0), + SOC_SINGLE("Mute ADC Serial Port Switch", CS42XX8_TXCTL, 3, 1, 0), + SOC_SINGLE("ADC Single Volume Control Switch", CS42XX8_TXCTL, 2, 1, 0), + SOC_ENUM("ADC Soft Ramp & Zero Cross Control Switch", adc_szc_enum), +}; + +static const struct snd_kcontrol_new cs42xx8_adc3_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("ADC3 Capture Volume", CS42XX8_VOLAIN5, + CS42XX8_VOLAIN6, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE("ADC3 Invert Switch", CS42XX8_ADCINV, 4, 5, 1, 0), + SOC_ENUM("ADC3 Single Ended Mode Switch", adc3_single_enum), +}; + +static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC1", "Playback", CS42XX8_PWRCTL, 1, 1), + SND_SOC_DAPM_DAC("DAC2", "Playback", CS42XX8_PWRCTL, 2, 1), + SND_SOC_DAPM_DAC("DAC3", "Playback", CS42XX8_PWRCTL, 3, 1), + SND_SOC_DAPM_DAC("DAC4", "Playback", CS42XX8_PWRCTL, 4, 1), + + SND_SOC_DAPM_OUTPUT("AOUT1L"), + SND_SOC_DAPM_OUTPUT("AOUT1R"), + SND_SOC_DAPM_OUTPUT("AOUT2L"), + SND_SOC_DAPM_OUTPUT("AOUT2R"), + SND_SOC_DAPM_OUTPUT("AOUT3L"), + SND_SOC_DAPM_OUTPUT("AOUT3R"), + SND_SOC_DAPM_OUTPUT("AOUT4L"), + SND_SOC_DAPM_OUTPUT("AOUT4R"), + + SND_SOC_DAPM_ADC("ADC1", "Capture", CS42XX8_PWRCTL, 5, 1), + SND_SOC_DAPM_ADC("ADC2", "Capture", CS42XX8_PWRCTL, 6, 1), + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + +}; + +static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADC3", "Capture", CS42XX8_PWRCTL, 7, 1), + + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R"), +}; + +static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = { + /* Playback */ + { "AOUT1L", NULL, "DAC1" }, + { "AOUT1R", NULL, "DAC1" }, + + { "AOUT2L", NULL, "DAC2" }, + { "AOUT2R", NULL, "DAC2" }, + + { "AOUT3L", NULL, "DAC3" }, + { "AOUT3R", NULL, "DAC3" }, + + { "AOUT4L", NULL, "DAC4" }, + { "AOUT4R", NULL, "DAC4" }, + + /* Capture */ + { "ADC1", NULL, "AIN1L" }, + { "ADC1", NULL, "AIN1R" }, + + { "ADC2", NULL, "AIN2L" }, + { "ADC2", NULL, "AIN2R" }, +}; + +static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = { + /* Capture */ + { "ADC3", NULL, "AIN3L" }, + { "ADC3", NULL, "AIN3R" }, +}; + +struct cs42xx8_ratios { + unsigned int mfreq; + unsigned int min_mclk; + unsigned int max_mclk; + unsigned int ratio[3]; +}; + +static const struct cs42xx8_ratios cs42xx8_ratios[] = { + { 0, 1029000, 12800000, {256, 128, 64} }, + { 2, 1536000, 19200000, {384, 192, 96} }, + { 4, 2048000, 25600000, {512, 256, 128} }, + { 6, 3072000, 38400000, {768, 384, 192} }, + { 8, 4096000, 51200000, {1024, 512, 256} }, +}; + +static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + + cs42xx8->sysclk = freq; + + return 0; +} + +static int cs42xx8_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int format) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + u32 val; + + /* Set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + val = CS42XX8_INTF_DAC_DIF_LEFTJ | CS42XX8_INTF_ADC_DIF_LEFTJ; + break; + case SND_SOC_DAIFMT_I2S: + val = CS42XX8_INTF_DAC_DIF_I2S | CS42XX8_INTF_ADC_DIF_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val = CS42XX8_INTF_DAC_DIF_RIGHTJ | CS42XX8_INTF_ADC_DIF_RIGHTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM; + break; + default: + dev_err(component->dev, "unsupported dai format\n"); + return -EINVAL; + } + + regmap_update_bits(cs42xx8->regmap, CS42XX8_INTF, + CS42XX8_INTF_DAC_DIF_MASK | + CS42XX8_INTF_ADC_DIF_MASK, val); + + if (cs42xx8->slave_mode[0] == cs42xx8->slave_mode[1]) { + /* Set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs42xx8->slave_mode[0] = true; + cs42xx8->slave_mode[1] = true; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs42xx8->slave_mode[0] = false; + cs42xx8->slave_mode[1] = false; + break; + default: + dev_err(component->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + } + + return 0; +} + +static int cs42xx8_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 ratio[2]; + u32 rate[2]; + u32 fm[2]; + u32 i, val, mask; + bool condition1, condition2; + + if (tx) + cs42xx8->tx_channels = params_channels(params); + + rate[tx] = params_rate(params); + rate[!tx] = cs42xx8->rate[!tx]; + + ratio[tx] = rate[tx] > 0 ? cs42xx8->sysclk / rate[tx] : 0; + ratio[!tx] = rate[!tx] > 0 ? cs42xx8->sysclk / rate[!tx] : 0; + + for (i = 0; i < 2; i++) { + if (cs42xx8->slave_mode[i]) { + fm[i] = CS42XX8_FM_AUTO; + } else { + if (rate[i] < 50000) + fm[i] = CS42XX8_FM_SINGLE; + else if (rate[i] > 50000 && rate[i] < 100000) + fm[i] = CS42XX8_FM_DOUBLE; + else if (rate[i] > 100000 && rate[i] < 200000) + fm[i] = CS42XX8_FM_QUAD; + else { + dev_err(component->dev, + "unsupported sample rate or rate combine\n"); + return -EINVAL; + } + } + } + + for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) { + condition1 = ((fm[tx] == CS42XX8_FM_AUTO) ? + (cs42xx8_ratios[i].ratio[0] == ratio[tx] || + cs42xx8_ratios[i].ratio[1] == ratio[tx] || + cs42xx8_ratios[i].ratio[2] == ratio[tx]) : + (cs42xx8_ratios[i].ratio[fm[tx]] == ratio[tx])) && + cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk && + cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk; + + if (ratio[tx] <= 0) + condition1 = true; + + condition2 = ((fm[!tx] == CS42XX8_FM_AUTO) ? + (cs42xx8_ratios[i].ratio[0] == ratio[!tx] || + cs42xx8_ratios[i].ratio[1] == ratio[!tx] || + cs42xx8_ratios[i].ratio[2] == ratio[!tx]) : + (cs42xx8_ratios[i].ratio[fm[!tx]] == ratio[!tx])); + + if (ratio[!tx] <= 0) + condition2 = true; + + if (condition1 && condition2) + break; + } + + if (i == ARRAY_SIZE(cs42xx8_ratios)) { + dev_err(component->dev, "unsupported sysclk ratio\n"); + return -EINVAL; + } + + cs42xx8->rate[tx] = params_rate(params); + + mask = CS42XX8_FUNCMOD_MFREQ_MASK; + val = cs42xx8_ratios[i].mfreq; + + regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD, + CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask, + CS42XX8_FUNCMOD_xC_FM(tx, fm[tx]) | val); + + return 0; +} + +static int cs42xx8_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + cs42xx8->rate[tx] = 0; + + regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD, + CS42XX8_FUNCMOD_xC_FM_MASK(tx), + CS42XX8_FUNCMOD_xC_FM(tx, CS42XX8_FM_AUTO)); + return 0; +} + +static int cs42xx8_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + u8 dac_unmute = cs42xx8->tx_channels ? + ~((0x1 << cs42xx8->tx_channels) - 1) : 0; + + regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, + mute ? CS42XX8_DACMUTE_ALL : dac_unmute); + + return 0; +} + +static const struct snd_soc_dai_ops cs42xx8_dai_ops = { + .set_fmt = cs42xx8_set_dai_fmt, + .set_sysclk = cs42xx8_set_dai_sysclk, + .hw_params = cs42xx8_hw_params, + .hw_free = cs42xx8_hw_free, + .digital_mute = cs42xx8_digital_mute, +}; + +static struct snd_soc_dai_driver cs42xx8_dai = { + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = CS42XX8_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = CS42XX8_FORMATS, + }, + .ops = &cs42xx8_dai_ops, +}; + +static const struct reg_default cs42xx8_reg[] = { + { 0x02, 0x00 }, /* Power Control */ + { 0x03, 0xF0 }, /* Functional Mode */ + { 0x04, 0x46 }, /* Interface Formats */ + { 0x05, 0x00 }, /* ADC Control & DAC De-Emphasis */ + { 0x06, 0x10 }, /* Transition Control */ + { 0x07, 0x00 }, /* DAC Channel Mute */ + { 0x08, 0x00 }, /* Volume Control AOUT1 */ + { 0x09, 0x00 }, /* Volume Control AOUT2 */ + { 0x0a, 0x00 }, /* Volume Control AOUT3 */ + { 0x0b, 0x00 }, /* Volume Control AOUT4 */ + { 0x0c, 0x00 }, /* Volume Control AOUT5 */ + { 0x0d, 0x00 }, /* Volume Control AOUT6 */ + { 0x0e, 0x00 }, /* Volume Control AOUT7 */ + { 0x0f, 0x00 }, /* Volume Control AOUT8 */ + { 0x10, 0x00 }, /* DAC Channel Invert */ + { 0x11, 0x00 }, /* Volume Control AIN1 */ + { 0x12, 0x00 }, /* Volume Control AIN2 */ + { 0x13, 0x00 }, /* Volume Control AIN3 */ + { 0x14, 0x00 }, /* Volume Control AIN4 */ + { 0x15, 0x00 }, /* Volume Control AIN5 */ + { 0x16, 0x00 }, /* Volume Control AIN6 */ + { 0x17, 0x00 }, /* ADC Channel Invert */ + { 0x18, 0x00 }, /* Status Control */ + { 0x1a, 0x00 }, /* Status Mask */ + { 0x1b, 0x00 }, /* MUTEC Pin Control */ +}; + +static bool cs42xx8_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42XX8_STATUS: + return true; + default: + return false; + } +} + +static bool cs42xx8_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42XX8_CHIPID: + case CS42XX8_STATUS: + return false; + default: + return true; + } +} + +static int rpmsg_cs42xx8_read(void *context, unsigned int reg, unsigned int *val) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg; + int err, reg_val; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = cs42xx8->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->header.cmd = GET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info); + reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data; + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + *val = reg_val; + return 0; +} + +static int rpmsg_cs42xx8_write(void *context, unsigned int reg, unsigned int val) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg; + int err; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = cs42xx8->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->param.buffer_size = val; + rpmsg->header.cmd = SET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info); + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + return 0; +} + +static struct regmap_config rpmsg_cs42xx8_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS42XX8_LASTREG, + .reg_defaults = cs42xx8_reg, + .num_reg_defaults = ARRAY_SIZE(cs42xx8_reg), + .volatile_reg = cs42xx8_volatile_register, + .writeable_reg = cs42xx8_writeable_register, + .cache_type = REGCACHE_RBTREE, + + .reg_read = rpmsg_cs42xx8_read, + .reg_write = rpmsg_cs42xx8_write, +}; + +static int cs42xx8_codec_probe(struct snd_soc_component *component) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + switch (cs42xx8->drvdata->num_adcs) { + case 3: + snd_soc_add_component_controls(component, cs42xx8_adc3_snd_controls, + ARRAY_SIZE(cs42xx8_adc3_snd_controls)); + snd_soc_dapm_new_controls(dapm, cs42xx8_adc3_dapm_widgets, + ARRAY_SIZE(cs42xx8_adc3_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, cs42xx8_adc3_dapm_routes, + ARRAY_SIZE(cs42xx8_adc3_dapm_routes)); + break; + default: + break; + } + + /* Mute all DAC channels */ + regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL); + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 0); + return 0; +} + +static const struct snd_soc_component_driver cs42xx8_driver = { + .probe = cs42xx8_codec_probe, + .controls = cs42xx8_snd_controls, + .num_controls = ARRAY_SIZE(cs42xx8_snd_controls), + .dapm_widgets = cs42xx8_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42xx8_dapm_widgets), + .dapm_routes = cs42xx8_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs42xx8_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int rpmsg_cs42xx8_codec_probe(struct platform_device *pdev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent); + struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data; + struct rpmsg_cs42xx8_priv *cs42xx8; + struct device *dev = &pdev->dev; + int ret, val, i; + + cs42xx8 = devm_kzalloc(&pdev->dev, sizeof(*cs42xx8), GFP_KERNEL); + if (cs42xx8 == NULL) + return -ENOMEM; + + cs42xx8->regmap = devm_regmap_init(&pdev->dev, NULL, + cs42xx8, &rpmsg_cs42xx8_regmap_config); + if (IS_ERR(cs42xx8->regmap)) + return PTR_ERR(cs42xx8->regmap); + + dev_set_drvdata(&pdev->dev, cs42xx8); + + cs42xx8->drvdata = devm_kzalloc(&pdev->dev, + sizeof(struct cs42xx8_driver_data), GFP_KERNEL); + if (!cs42xx8->drvdata) + return -ENOMEM; + + cs42xx8->rpmsg_i2s = rpmsg_i2s; + + memcpy(cs42xx8->drvdata->name, pdata->name, 32); + cs42xx8->drvdata->num_adcs = pdata->num_adcs; + cs42xx8->audioindex = pdata->audioindex; + cs42xx8->reset_gpio = of_get_named_gpio(pdev->dev.parent->of_node, + "reset-gpio", 0); + if (gpio_is_valid(cs42xx8->reset_gpio)) { + ret = devm_gpio_request_one(dev, cs42xx8->reset_gpio, + GPIOF_OUT_INIT_LOW, "cs42xx8 reset"); + if (ret) { + dev_err(dev, "unable to get reset gpio\n"); + return ret; + } + gpio_set_value_cansleep(cs42xx8->reset_gpio, 1); + } + + cs42xx8->clk = devm_clk_get(pdev->dev.parent, "mclk"); + if (IS_ERR(cs42xx8->clk)) { + dev_err(dev, "failed to get the clock: %ld\n", + PTR_ERR(cs42xx8->clk)); + return -EINVAL; + } + + cs42xx8->sysclk = clk_get_rate(cs42xx8->clk); + + if (of_property_read_bool(pdev->dev.parent->of_node, "fsl,txm-rxs")) { + /* 0 -- rx, 1 -- tx */ + cs42xx8->slave_mode[0] = true; + cs42xx8->slave_mode[1] = false; + } + + if (of_property_read_bool(pdev->dev.parent->of_node, "fsl,txs-rxm")) { + /* 0 -- rx, 1 -- tx */ + cs42xx8->slave_mode[0] = false; + cs42xx8->slave_mode[1] = true; + } + + for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++) + cs42xx8->supplies[i].supply = cs42xx8_supply_names[i]; + + ret = devm_regulator_bulk_get(pdev->dev.parent, + ARRAY_SIZE(cs42xx8->supplies), cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + /* Make sure hardware reset done */ + usleep_range(5000, 10000); + + /* + * We haven't marked the chip revision as volatile due to + * sharing a register with the right input volume; explicitly + * bypass the cache to read it. + */ + regcache_cache_bypass(cs42xx8->regmap, true); + + /* Validate the chip ID */ + ret = regmap_read(cs42xx8->regmap, CS42XX8_CHIPID, &val); + if (ret < 0) { + dev_err(dev, "failed to get device ID, ret = %d", ret); + goto err_enable; + } + + /* The top four bits of the chip ID should be 0000 */ + if (((val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4) != 0x00) { + dev_err(dev, "unmatched chip ID: %d\n", + (val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4); + ret = -EINVAL; + goto err_enable; + } + + dev_info(dev, "found device, revision %X\n", + val & CS42XX8_CHIPID_REV_ID_MASK); + + regcache_cache_bypass(cs42xx8->regmap, false); + + cs42xx8_dai.name = cs42xx8->drvdata->name; + + /* Each adc supports stereo input */ + cs42xx8_dai.capture.channels_max = cs42xx8->drvdata->num_adcs * 2; + + pm_runtime_enable(dev); + pm_request_idle(dev); + + ret = devm_snd_soc_register_component(dev, &cs42xx8_driver, &cs42xx8_dai, 1); + if (ret) { + dev_err(dev, "failed to register codec:%d\n", ret); + goto err_enable; + } + + regcache_cache_only(cs42xx8->regmap, true); + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + + return ret; +} + +#ifdef CONFIG_PM +static int cs42xx8_runtime_resume(struct device *dev) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(cs42xx8->clk); + if (ret) { + dev_err(dev, "failed to enable mclk: %d\n", ret); + return ret; + } + + if (gpio_is_valid(cs42xx8->reset_gpio)) { + gpio_set_value_cansleep(cs42xx8->reset_gpio, 0); + gpio_set_value_cansleep(cs42xx8->reset_gpio, 1); + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + goto err_clk; + } + + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 1); + /* Make sure hardware reset done */ + usleep_range(5000, 10000); + + regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL, + CS42XX8_PWRCTL_PDN_MASK, 0); + + regcache_cache_only(cs42xx8->regmap, false); + + ret = regcache_sync(cs42xx8->regmap); + if (ret) { + dev_err(dev, "failed to sync regmap: %d\n", ret); + goto err_bulk; + } + + return 0; + +err_bulk: + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); +err_clk: + clk_disable_unprepare(cs42xx8->clk); + + return ret; +} + +static int cs42xx8_runtime_suspend(struct device *dev) +{ + struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev); + + regcache_cache_only(cs42xx8->regmap, true); + + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + + clk_disable_unprepare(cs42xx8->clk); + + return 0; +} +#endif + +const struct dev_pm_ops rpmsg_cs42xx8_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL) +}; + +static int rpmsg_cs42xx8_codec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver rpmsg_cs42xx8_codec_driver = { + .driver = { + .name = RPMSG_CODEC_DRV_NAME_CS42888, + .pm = &rpmsg_cs42xx8_pm, + }, + .probe = rpmsg_cs42xx8_codec_probe, + .remove = rpmsg_cs42xx8_codec_remove, +}; + +module_platform_driver(rpmsg_cs42xx8_codec_driver); + +MODULE_DESCRIPTION("Cirrus Logic CS42448/CS42888 ALSA SoC Codec Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rpmsg_cs42xx8.h b/sound/soc/codecs/rpmsg_cs42xx8.h new file mode 100644 index 000000000000..682295272f49 --- /dev/null +++ b/sound/soc/codecs/rpmsg_cs42xx8.h @@ -0,0 +1,232 @@ +/* + * cs42xx8.h - Cirrus Logic CS42448/CS42888 Audio CODEC driver header file + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <Guangyu.Chen@freescale.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _RPMSG_CS42XX8_H +#define _RPMSG_CS42XX8_H + +struct cs42xx8_driver_data { + char name[32]; + int num_adcs; +}; + +/* CS42888 register map */ +#define CS42XX8_CHIPID 0x01 /* Chip ID */ +#define CS42XX8_PWRCTL 0x02 /* Power Control */ +#define CS42XX8_FUNCMOD 0x03 /* Functional Mode */ +#define CS42XX8_INTF 0x04 /* Interface Formats */ +#define CS42XX8_ADCCTL 0x05 /* ADC Control */ +#define CS42XX8_TXCTL 0x06 /* Transition Control */ +#define CS42XX8_DACMUTE 0x07 /* DAC Mute Control */ +#define CS42XX8_VOLAOUT1 0x08 /* Volume Control AOUT1 */ +#define CS42XX8_VOLAOUT2 0x09 /* Volume Control AOUT2 */ +#define CS42XX8_VOLAOUT3 0x0A /* Volume Control AOUT3 */ +#define CS42XX8_VOLAOUT4 0x0B /* Volume Control AOUT4 */ +#define CS42XX8_VOLAOUT5 0x0C /* Volume Control AOUT5 */ +#define CS42XX8_VOLAOUT6 0x0D /* Volume Control AOUT6 */ +#define CS42XX8_VOLAOUT7 0x0E /* Volume Control AOUT7 */ +#define CS42XX8_VOLAOUT8 0x0F /* Volume Control AOUT8 */ +#define CS42XX8_DACINV 0x10 /* DAC Channel Invert */ +#define CS42XX8_VOLAIN1 0x11 /* Volume Control AIN1 */ +#define CS42XX8_VOLAIN2 0x12 /* Volume Control AIN2 */ +#define CS42XX8_VOLAIN3 0x13 /* Volume Control AIN3 */ +#define CS42XX8_VOLAIN4 0x14 /* Volume Control AIN4 */ +#define CS42XX8_VOLAIN5 0x15 /* Volume Control AIN5 */ +#define CS42XX8_VOLAIN6 0x16 /* Volume Control AIN6 */ +#define CS42XX8_ADCINV 0x17 /* ADC Channel Invert */ +#define CS42XX8_STATUSCTL 0x18 /* Status Control */ +#define CS42XX8_STATUS 0x19 /* Status */ +#define CS42XX8_STATUSM 0x1A /* Status Mask */ +#define CS42XX8_MUTEC 0x1B /* MUTEC Pin Control */ + +#define CS42XX8_FIRSTREG CS42XX8_CHIPID +#define CS42XX8_LASTREG CS42XX8_MUTEC +#define CS42XX8_NUMREGS (CS42XX8_LASTREG - CS42XX8_FIRSTREG + 1) +#define CS42XX8_I2C_INCR 0x80 + +/* Chip I.D. and Revision Register (Address 01h) */ +#define CS42XX8_CHIPID_CHIP_ID_MASK 0xF0 +#define CS42XX8_CHIPID_REV_ID_MASK 0x0F + +/* Power Control (Address 02h) */ +#define CS42XX8_PWRCTL_PDN_ADC3_SHIFT 7 +#define CS42XX8_PWRCTL_PDN_ADC3_MASK (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC3 (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC2_SHIFT 6 +#define CS42XX8_PWRCTL_PDN_ADC2_MASK (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC2 (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC1_SHIFT 5 +#define CS42XX8_PWRCTL_PDN_ADC1_MASK (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC1 (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC4_SHIFT 4 +#define CS42XX8_PWRCTL_PDN_DAC4_MASK (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC4 (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC3_SHIFT 3 +#define CS42XX8_PWRCTL_PDN_DAC3_MASK (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC3 (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC2_SHIFT 2 +#define CS42XX8_PWRCTL_PDN_DAC2_MASK (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC2 (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC1_SHIFT 1 +#define CS42XX8_PWRCTL_PDN_DAC1_MASK (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC1 (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_SHIFT 0 +#define CS42XX8_PWRCTL_PDN_MASK (1 << CS42XX8_PWRCTL_PDN_SHIFT) +#define CS42XX8_PWRCTL_PDN (1 << CS42XX8_PWRCTL_PDN_SHIFT) + +/* Functional Mode (Address 03h) */ +#define CS42XX8_FUNCMOD_DAC_FM_SHIFT 6 +#define CS42XX8_FUNCMOD_DAC_FM_WIDTH 2 +#define CS42XX8_FUNCMOD_DAC_FM_MASK (((1 << CS42XX8_FUNCMOD_DAC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_DAC_FM_SHIFT) +#define CS42XX8_FUNCMOD_DAC_FM(v) ((v) << CS42XX8_FUNCMOD_DAC_FM_SHIFT) +#define CS42XX8_FUNCMOD_ADC_FM_SHIFT 4 +#define CS42XX8_FUNCMOD_ADC_FM_WIDTH 2 +#define CS42XX8_FUNCMOD_ADC_FM_MASK (((1 << CS42XX8_FUNCMOD_ADC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_ADC_FM_SHIFT) +#define CS42XX8_FUNCMOD_ADC_FM(v) ((v) << CS42XX8_FUNCMOD_ADC_FM_SHIFT) +#define CS42XX8_FUNCMOD_xC_FM_MASK(x) ((x) ? CS42XX8_FUNCMOD_DAC_FM_MASK : CS42XX8_FUNCMOD_ADC_FM_MASK) +#define CS42XX8_FUNCMOD_xC_FM(x, v) ((x) ? CS42XX8_FUNCMOD_DAC_FM(v) : CS42XX8_FUNCMOD_ADC_FM(v)) +#define CS42XX8_FUNCMOD_MFREQ_SHIFT 1 +#define CS42XX8_FUNCMOD_MFREQ_WIDTH 3 +#define CS42XX8_FUNCMOD_MFREQ_MASK (((1 << CS42XX8_FUNCMOD_MFREQ_WIDTH) - 1) << CS42XX8_FUNCMOD_MFREQ_SHIFT) +#define CS42XX8_FUNCMOD_MFREQ_256(s) ((0 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_384(s) ((1 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_512(s) ((2 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_768(s) ((3 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_1024(s) ((4 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) + +#define CS42XX8_FM_SINGLE 0 +#define CS42XX8_FM_DOUBLE 1 +#define CS42XX8_FM_QUAD 2 +#define CS42XX8_FM_AUTO 3 + +/* Interface Formats (Address 04h) */ +#define CS42XX8_INTF_FREEZE_SHIFT 7 +#define CS42XX8_INTF_FREEZE_MASK (1 << CS42XX8_INTF_FREEZE_SHIFT) +#define CS42XX8_INTF_FREEZE (1 << CS42XX8_INTF_FREEZE_SHIFT) +#define CS42XX8_INTF_AUX_DIF_SHIFT 6 +#define CS42XX8_INTF_AUX_DIF_MASK (1 << CS42XX8_INTF_AUX_DIF_SHIFT) +#define CS42XX8_INTF_AUX_DIF (1 << CS42XX8_INTF_AUX_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_SHIFT 3 +#define CS42XX8_INTF_DAC_DIF_WIDTH 3 +#define CS42XX8_INTF_DAC_DIF_MASK (((1 << CS42XX8_INTF_DAC_DIF_WIDTH) - 1) << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_LEFTJ (0 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_I2S (1 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_RIGHTJ (2 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_ONELINE_20 (4 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_ONELINE_24 (5 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_TDM (6 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_SHIFT 0 +#define CS42XX8_INTF_ADC_DIF_WIDTH 3 +#define CS42XX8_INTF_ADC_DIF_MASK (((1 << CS42XX8_INTF_ADC_DIF_WIDTH) - 1) << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_LEFTJ (0 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_I2S (1 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_RIGHTJ (2 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_ONELINE_20 (4 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_ONELINE_24 (5 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_TDM (6 << CS42XX8_INTF_ADC_DIF_SHIFT) + +/* ADC Control & DAC De-Emphasis (Address 05h) */ +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT 7 +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_MASK (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT) +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT) +#define CS42XX8_ADCCTL_DAC_DEM_SHIFT 5 +#define CS42XX8_ADCCTL_DAC_DEM_MASK (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT) +#define CS42XX8_ADCCTL_DAC_DEM (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT) +#define CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT 4 +#define CS42XX8_ADCCTL_ADC1_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC1_SINGLE (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT 3 +#define CS42XX8_ADCCTL_ADC2_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC2_SINGLE (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT 2 +#define CS42XX8_ADCCTL_ADC3_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC3_SINGLE (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_AIN5_MUX_SHIFT 1 +#define CS42XX8_ADCCTL_AIN5_MUX_MASK (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN5_MUX (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN6_MUX_SHIFT 0 +#define CS42XX8_ADCCTL_AIN6_MUX_MASK (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN6_MUX (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT) + +/* Transition Control (Address 06h) */ +#define CS42XX8_TXCTL_DAC_SNGVOL_SHIFT 7 +#define CS42XX8_TXCTL_DAC_SNGVOL_MASK (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_DAC_SNGVOL (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SHIFT 5 +#define CS42XX8_TXCTL_DAC_SZC_WIDTH 2 +#define CS42XX8_TXCTL_DAC_SZC_MASK (((1 << CS42XX8_TXCTL_DAC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_IC (0 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_ZC (1 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SR (2 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SRZC (3 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_AMUTE_SHIFT 4 +#define CS42XX8_TXCTL_AMUTE_MASK (1 << CS42XX8_TXCTL_AMUTE_SHIFT) +#define CS42XX8_TXCTL_AMUTE (1 << CS42XX8_TXCTL_AMUTE_SHIFT) +#define CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT 3 +#define CS42XX8_TXCTL_MUTE_ADC_SP_MASK (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT) +#define CS42XX8_TXCTL_MUTE_ADC_SP (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT) +#define CS42XX8_TXCTL_ADC_SNGVOL_SHIFT 2 +#define CS42XX8_TXCTL_ADC_SNGVOL_MASK (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_ADC_SNGVOL (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SHIFT 0 +#define CS42XX8_TXCTL_ADC_SZC_MASK (((1 << CS42XX8_TXCTL_ADC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_IC (0 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_ZC (1 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SR (2 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SRZC (3 << CS42XX8_TXCTL_ADC_SZC_SHIFT) + +/* DAC Channel Mute (Address 07h) */ +#define CS42XX8_DACMUTE_AOUT(n) (0x1 << n) +#define CS42XX8_DACMUTE_ALL 0xff + +/* Status Control (Address 18h)*/ +#define CS42XX8_STATUSCTL_INI_SHIFT 2 +#define CS42XX8_STATUSCTL_INI_WIDTH 2 +#define CS42XX8_STATUSCTL_INI_MASK (((1 << CS42XX8_STATUSCTL_INI_WIDTH) - 1) << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_ACTIVE_HIGH (0 << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_ACTIVE_LOW (1 << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_OPEN_DRAIN (2 << CS42XX8_STATUSCTL_INI_SHIFT) + +/* Status (Address 19h)*/ +#define CS42XX8_STATUS_DAC_CLK_ERR_SHIFT 4 +#define CS42XX8_STATUS_DAC_CLK_ERR_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_SHIFT) +#define CS42XX8_STATUS_ADC_CLK_ERR_SHIFT 3 +#define CS42XX8_STATUS_ADC_CLK_ERR_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_SHIFT) +#define CS42XX8_STATUS_ADC3_OVFL_SHIFT 2 +#define CS42XX8_STATUS_ADC3_OVFL_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_SHIFT) +#define CS42XX8_STATUS_ADC2_OVFL_SHIFT 1 +#define CS42XX8_STATUS_ADC2_OVFL_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_SHIFT) +#define CS42XX8_STATUS_ADC1_OVFL_SHIFT 0 +#define CS42XX8_STATUS_ADC1_OVFL_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_SHIFT) + +/* Status Mask (Address 1Ah) */ +#define CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT 4 +#define CS42XX8_STATUS_DAC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT) +#define CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT 3 +#define CS42XX8_STATUS_ADC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT) +#define CS42XX8_STATUS_ADC3_OVFL_M_SHIFT 2 +#define CS42XX8_STATUS_ADC3_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_M_SHIFT) +#define CS42XX8_STATUS_ADC2_OVFL_M_SHIFT 1 +#define CS42XX8_STATUS_ADC2_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_M_SHIFT) +#define CS42XX8_STATUS_ADC1_OVFL_M_SHIFT 0 +#define CS42XX8_STATUS_ADC1_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_M_SHIFT) + +/* MUTEC Pin Control (Address 1Bh) */ +#define CS42XX8_MUTEC_MCPOLARITY_SHIFT 1 +#define CS42XX8_MUTEC_MCPOLARITY_MASK (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_LOW (0 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_HIGH (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT 0 +#define CS42XX8_MUTEC_MUTEC_ACTIVE_MASK (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT) +#define CS42XX8_MUTEC_MUTEC_ACTIVE (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT) +#endif /* _CS42XX8_H */ diff --git a/sound/soc/codecs/rpmsg_wm8960.c b/sound/soc/codecs/rpmsg_wm8960.c new file mode 100644 index 000000000000..e56eff26823f --- /dev/null +++ b/sound/soc/codecs/rpmsg_wm8960.c @@ -0,0 +1,1512 @@ +/* + * Copyright 2018 NXP + * + * Copyright 2007-11 Wolfson Microelectronics, plc + * + * Author: Liam Girdwood + * + * 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/clk.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/wm8960.h> +#include "../fsl/fsl_rpmsg_i2s.h" +#include "wm8960.h" +#include "rpmsg_wm8960.h" + +static bool is_pll_freq_available(unsigned int source, unsigned int target); +static int wm8960_set_pll(struct snd_soc_component *component, + unsigned int freq_in, unsigned int freq_out); +/* + * wm8960 register cache + * We can't read the WM8960 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8960_reg_defaults[] = { + { 0x0, 0x00a7 }, + { 0x1, 0x00a7 }, + { 0x2, 0x0000 }, + { 0x3, 0x0000 }, + { 0x4, 0x0000 }, + { 0x5, 0x0008 }, + { 0x6, 0x0000 }, + { 0x7, 0x000a }, + { 0x8, 0x01c0 }, + { 0x9, 0x0000 }, + { 0xa, 0x00ff }, + { 0xb, 0x00ff }, + + { 0x10, 0x0000 }, + { 0x11, 0x007b }, + { 0x12, 0x0100 }, + { 0x13, 0x0032 }, + { 0x14, 0x0000 }, + { 0x15, 0x00c3 }, + { 0x16, 0x00c3 }, + { 0x17, 0x01c0 }, + { 0x18, 0x0000 }, + { 0x19, 0x0000 }, + { 0x1a, 0x0000 }, + { 0x1b, 0x0000 }, + { 0x1c, 0x0000 }, + { 0x1d, 0x0000 }, + + { 0x20, 0x0100 }, + { 0x21, 0x0100 }, + { 0x22, 0x0050 }, + + { 0x25, 0x0050 }, + { 0x26, 0x0000 }, + { 0x27, 0x0000 }, + { 0x28, 0x0000 }, + { 0x29, 0x0000 }, + { 0x2a, 0x0040 }, + { 0x2b, 0x0000 }, + { 0x2c, 0x0000 }, + { 0x2d, 0x0050 }, + { 0x2e, 0x0050 }, + { 0x2f, 0x0000 }, + { 0x30, 0x0002 }, + { 0x31, 0x0037 }, + + { 0x33, 0x0080 }, + { 0x34, 0x0008 }, + { 0x35, 0x0031 }, + { 0x36, 0x0026 }, + { 0x37, 0x00e9 }, +}; + +static bool wm8960_volatile(struct device *dev, unsigned int reg) +{ + struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev); + + if (!wm8960->mclk) + return true; + + switch (reg) { + case WM8960_RESET: + return true; + default: + return false; + } +} + + +/* enumerated controls */ +static const char * const wm8960_polarity[] = {"No Inversion", "Left Inverted", + "Right Inverted", "Stereo Inversion"}; +static const char * const wm8960_3d_upper_cutoff[] = {"High", "Low"}; +static const char * const wm8960_3d_lower_cutoff[] = {"Low", "High"}; +static const char * const wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; +static const char * const wm8960_alcmode[] = {"ALC", "Limiter"}; +static const char * const wm8960_adc_data_output_sel[] = { + "Left Data = Left ADC; Right Data = Right ADC", + "Left Data = Left ADC; Right Data = Left ADC", + "Left Data = Right ADC; Right Data = Right ADC", + "Left Data = Right ADC; Right Data = Left ADC", +}; +static const char * const wm8960_dmonomix[] = {"Stereo", "Mono"}; + +static const struct soc_enum wm8960_enum[] = { + SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), + SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), + SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), + SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 2, 4, wm8960_adc_data_output_sel), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix), +}; + +static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8960_set_deemph(struct snd_soc_component *component) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8960->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8960->lrclk) < + abs(deemph_settings[best] - wm8960->lrclk)) + best = i; + } + + val = best << 1; + } else { + val = 0; + } + + dev_dbg(component->dev, "Set deemphasis %d\n", val); + + return snd_soc_component_update_bits(component, WM8960_DACCTL1, + 0x6, val); +} + +static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8960->deemph; + return 0; +} + +static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + + if (deemph > 1) + return -EINVAL; + + wm8960->deemph = deemph; + + return wm8960_set_deemph(component); +} + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(lineinboost_tlv, -1500, 300, 1); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 1300, 0), + 2, 3, TLV_DB_SCALE_ITEM(2000, 900, 0), +); + +static const struct snd_kcontrol_new wm8960_snd_controls[] = { +SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, + 0, 63, 0, inpga_tlv), +SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, + 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, + 7, 1, 1), + +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume", + WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume", + WM8960_INBMIX1, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume", + WM8960_INBMIX2, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume", + WM8960_INBMIX2, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT1 Volume", + WM8960_RINPATH, 4, 3, 0, micboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume", + WM8960_LINPATH, 4, 3, 0, micboost_tlv), + +SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, + 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, + 7, 1, 0), +SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), + +SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), +SOC_ENUM("ADC Polarity", wm8960_enum[0]), +SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), + +SOC_ENUM("DAC Polarity", wm8960_enum[1]), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8960_get_deemph, wm8960_put_deemph), + +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]), +SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), + +SOC_ENUM("ALC Function", wm8960_enum[4]), +SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), +SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), +SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), +SOC_ENUM("ALC Mode", wm8960_enum[5]), +SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), + +SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), +SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), + +SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC, + 0, 255, 0, adc_tlv), + +SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", + WM8960_BYPASS1, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", + WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", + WM8960_BYPASS2, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", + WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), + +SOC_ENUM("ADC Data Output Select", wm8960_enum[6]), +SOC_ENUM("DAC Mono Mix", wm8960_enum[7]), +}; + +static const struct snd_kcontrol_new wm8960_lin_boost[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_lin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin_boost[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_routput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_mono_out[] = { +SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("RINPUT3"), + +SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, + wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), +SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, + wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, + wm8960_lin, ARRAY_SIZE(wm8960_lin)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, + wm8960_rin, ARRAY_SIZE(wm8960_rin)), + +SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0), + +SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), +SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, + &wm8960_loutput_mixer[0], + ARRAY_SIZE(wm8960_loutput_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, + &wm8960_routput_mixer[0], + ARRAY_SIZE(wm8960_routput_mixer)), + +SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("OUT3"), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { +SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, + &wm8960_mono_out[0], + ARRAY_SIZE(wm8960_mono_out)), +}; + +/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { +SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, + { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, + { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, + + { "Left Input Mixer", "Boost Switch", "Left Boost Mixer" }, + { "Left Input Mixer", "Boost Switch", "LINPUT1" }, /* Really Boost Switch */ + { "Left Input Mixer", NULL, "LINPUT2" }, + { "Left Input Mixer", NULL, "LINPUT3" }, + + { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, + { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, + { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, + + { "Right Input Mixer", "Boost Switch", "Right Boost Mixer" }, + { "Right Input Mixer", "Boost Switch", "RINPUT1" }, /* Really Boost Switch */ + { "Right Input Mixer", NULL, "RINPUT2" }, + { "Right Input Mixer", NULL, "RINPUT3" }, + + { "Left ADC", NULL, "Left Input Mixer" }, + { "Right ADC", NULL, "Right Input Mixer" }, + + { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, + { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" }, + { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, + + { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, + { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" }, + { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, + + { "LOUT1 PGA", NULL, "Left Output Mixer" }, + { "ROUT1 PGA", NULL, "Right Output Mixer" }, + + { "HP_L", NULL, "LOUT1 PGA" }, + { "HP_R", NULL, "ROUT1 PGA" }, + + { "Left Speaker PGA", NULL, "Left Output Mixer" }, + { "Right Speaker PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker Output", NULL, "Left Speaker PGA" }, + { "Right Speaker Output", NULL, "Right Speaker PGA" }, + + { "SPK_LN", NULL, "Left Speaker Output" }, + { "SPK_LP", NULL, "Left Speaker Output" }, + { "SPK_RN", NULL, "Right Speaker Output" }, + { "SPK_RP", NULL, "Right Speaker Output" }, +}; + +static const struct snd_soc_dapm_route audio_paths_out3[] = { + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, + + { "OUT3", NULL, "Mono Output Mixer", } +}; + +static const struct snd_soc_dapm_route audio_paths_capless[] = { + { "HP_L", NULL, "OUT3 VMID" }, + { "HP_R", NULL, "OUT3 VMID" }, + + { "OUT3 VMID", NULL, "Left Output Mixer" }, + { "OUT3 VMID", NULL, "Right Output Mixer" }, +}; + +static int wm8960_add_widgets(struct snd_soc_component *component) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + struct wm8960_data *pdata = &wm8960->pdata; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct snd_soc_dapm_widget *w; + + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, + ARRAY_SIZE(wm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); + + /* In capless mode OUT3 is used to provide VMID for the + * headphone outputs, otherwise it is used as a mono mixer. + */ + if (pdata && pdata->capless) { + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, + ARRAY_SIZE(wm8960_dapm_widgets_capless)); + + snd_soc_dapm_add_routes(dapm, audio_paths_capless, + ARRAY_SIZE(audio_paths_capless)); + } else { + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, + ARRAY_SIZE(wm8960_dapm_widgets_out3)); + + snd_soc_dapm_add_routes(dapm, audio_paths_out3, + ARRAY_SIZE(audio_paths_out3)); + } + + /* We need to power up the headphone output stage out of + * sequence for capless mode. To save scanning the widget + * list each time to find the desired power state do so now + * and save the result. + */ + list_for_each_entry(w, &component->card->widgets, list) { + if (w->dapm != dapm) + continue; + if (strcmp(w->name, "LOUT1 PGA") == 0) + wm8960->lout1 = w; + if (strcmp(w->name, "ROUT1 PGA") == 0) + wm8960->rout1 = w; + if (strcmp(w->name, "OUT3 VMID") == 0) + wm8960->out3 = w; + } + + return 0; +} + +static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + snd_soc_component_write(component, WM8960_IFACE1, iface); + return 0; +} + +static struct { + int rate; + unsigned int val; +} alc_rates[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11025, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + +/* -1 for reserved value */ +static const int sysclk_divs[] = { 1, -1, 2, -1 }; + +/* Multiply 256 for internal 256 div */ +static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 }; + +/* Multiply 10 to eliminate decimials */ +static const int bclk_divs[] = { + 10, 15, 20, 30, 40, 55, 60, 80, 110, + 120, 160, 220, 240, 320, 320, 320 +}; + +/** + * wm8960_configure_sysclk - checks if there is a sysclk frequency available + * The sysclk must be chosen such that: + * - sysclk = MCLK / sysclk_divs + * - lrclk = sysclk / dac_divs + * - 10 * bclk = sysclk / bclk_divs + * + * @wm8960_priv: wm8960 codec private data + * @mclk: MCLK used to derive sysclk + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no sysclk frequency available found + * >=0, in case we could derive bclk and lrclk from sysclk using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_sysclk(struct rpmsg_wm8960_priv *wm8960, int mclk, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + int sysclk, bclk, lrclk; + int i, j, k; + int diff; + + /* marker for no match */ + *bclk_idx = -1; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + /* check if the sysclk frequency is available. */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + sysclk = mclk / sysclk_divs[i]; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + if (sysclk != dac_divs[j] * lrclk) + continue; + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + break; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + return *bclk_idx; +} + +/** + * wm8960_configure_pll - checks if there is a PLL out frequency available + * The PLL out frequency must be chosen such that: + * - sysclk = lrclk * dac_divs + * - freq_out = sysclk * sysclk_divs + * - 10 * sysclk = bclk * bclk_divs + * + * @component: component structure + * @freq_in: input frequency used to derive freq out via PLL + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no PLL frequency out available was found + * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + int sysclk, bclk, lrclk, freq_out; + int diff, best_freq_out = 0; + int i, j, k; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + *bclk_idx = *dac_idx = *sysclk_idx = -1; + + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + sysclk = lrclk * dac_divs[j]; + freq_out = sysclk * sysclk_divs[i]; + + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + if (!is_pll_freq_available(freq_in, freq_out)) + continue; + + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + best_freq_out = freq_out; + break; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + + if (*bclk_idx != -1) + wm8960_set_pll(component, freq_in, best_freq_out); + + return *bclk_idx; +} +static int wm8960_configure_clocking(struct snd_soc_component *component) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + int freq_out, freq_in; + u16 iface1 = snd_soc_component_read32(component, WM8960_IFACE1); + int i, j, k; + int ret; + + if (!(iface1 & (1<<6))) { + dev_dbg(component->dev, + "Codec is slave mode, no need to configure clock\n"); + return 0; + } + + if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) { + dev_err(component->dev, "No MCLK configured\n"); + return -EINVAL; + } + + freq_in = wm8960->freq_in; + /* + * If it's sysclk auto mode, check if the MCLK can provide sysclk or + * not. If MCLK can provide sysclk, using MCLK to provide sysclk + * directly. Otherwise, auto select a available pll out frequency + * and set PLL. + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO) { + /* disable the PLL and using MCLK to provide sysclk */ + wm8960_set_pll(component, 0, 0); + freq_out = freq_in; + } else if (wm8960->sysclk) { + freq_out = wm8960->sysclk; + } else { + dev_err(component->dev, "No SYSCLK configured\n"); + return -EINVAL; + } + + if (wm8960->clk_id != WM8960_SYSCLK_PLL) { + ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k); + if (ret >= 0) { + goto configure_clock; + } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) { + dev_err(component->dev, "failed to configure clock\n"); + return -EINVAL; + } + } + + ret = wm8960_configure_pll(component, freq_in, &i, &j, &k); + if (ret < 0) { + dev_err(component->dev, "failed to configure clock via PLL\n"); + return -EINVAL; + } + +configure_clock: + /* configure sysclk clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK1, 3 << 1, i << 1); + + /* configure frame clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 3, j << 3); + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 6, j << 6); + + /* configure bit clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK2, 0xf, k); + + return 0; +} + +static int wm8960_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read32(component, WM8960_IFACE1) & 0xfff3; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i; + + wm8960->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + wm8960->bclk *= 2; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + case 32: + /* right justify mode does not support 32 word length */ + if ((iface & 0x3) != 0) { + iface |= 0x000c; + break; + } + /* fall through */ + default: + dev_err(component->dev, "unsupported width %d\n", + params_width(params)); + return -EINVAL; + } + + wm8960->lrclk = params_rate(params); + /* Update filters for the new rate */ + if (tx) { + wm8960_set_deemph(component); + } else { + for (i = 0; i < ARRAY_SIZE(alc_rates); i++) + if (alc_rates[i].rate == params_rate(params)) + snd_soc_component_update_bits(component, + WM8960_ADDCTL3, 0x7, + alc_rates[i].val); + } + + /* set iface */ + snd_soc_component_write(component, WM8960_IFACE1, iface); + + wm8960->is_stream_in_use[tx] = true; + + if (!wm8960->is_stream_in_use[!tx]) + return wm8960_configure_clocking(component); + + return 0; +} + +static int wm8960_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + wm8960->is_stream_in_use[tx] = false; + + return 0; +} + +static int wm8960_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + if (mute) + snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0x8); + else + snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0); + return 0; +} + +static int wm8960_set_bias_level_out3(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_STANDBY: + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(component); + if (ret) + return ret; + + /* Set VMID to 2x50k */ + snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x80); + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(component, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + break; + + default: + break; + } + + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_sync(wm8960->regmap); + + /* Enable anti-pop features */ + snd_soc_component_write(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Enable & ramp VMID at 2x50k */ + snd_soc_component_update_bits(component, WM8960_POWER1, 0x80, 0x80); + msleep(100); + + /* Enable VREF */ + snd_soc_component_update_bits(component, WM8960_POWER1, WM8960_VREF, + WM8960_VREF); + + /* Disable anti-pop features */ + snd_soc_component_write(component, WM8960_APOP1, WM8960_BUFIOEN); + } + + /* Set VMID to 2x250k */ + snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x100); + break; + + case SND_SOC_BIAS_OFF: + /* Enable anti-pop features */ + snd_soc_component_write(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Disable VMID and VREF, let them discharge */ + snd_soc_component_write(component, WM8960_POWER1, 0); + msleep(600); + break; + } + + return 0; +} + +static int wm8960_set_bias_level_capless(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2); + int reg, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_STANDBY: + /* Enable anti pop mode */ + snd_soc_component_update_bits(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ + reg = 0; + if (wm8960->lout1 && wm8960->lout1->power) + reg |= WM8960_PWR2_LOUT1; + if (wm8960->rout1 && wm8960->rout1->power) + reg |= WM8960_PWR2_ROUT1; + if (wm8960->out3 && wm8960->out3->power) + reg |= WM8960_PWR2_OUT3; + snd_soc_component_update_bits(component, WM8960_POWER2, + WM8960_PWR2_LOUT1 | + WM8960_PWR2_ROUT1 | + WM8960_PWR2_OUT3, reg); + + /* Enable VMID at 2*50k */ + snd_soc_component_update_bits(component, WM8960_POWER1, + WM8960_VMID_MASK, 0x80); + + /* Ramp */ + msleep(100); + + /* Enable VREF */ + snd_soc_component_update_bits(component, WM8960_POWER1, + WM8960_VREF, WM8960_VREF); + + msleep(100); + + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(component); + if (ret) + return ret; + + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(component, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + + /* Enable anti-pop mode */ + snd_soc_component_update_bits(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Disable VMID and VREF */ + snd_soc_component_update_bits(component, WM8960_POWER1, + WM8960_VREF | WM8960_VMID_MASK, 0); + break; + + case SND_SOC_BIAS_OFF: + regcache_sync(wm8960->regmap); + break; + default: + break; + } + break; + + case SND_SOC_BIAS_STANDBY: + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_PREPARE: + /* Disable HP discharge */ + snd_soc_component_update_bits(component, WM8960_APOP2, + WM8960_DISOP | WM8960_DRES_MASK, + 0); + + /* Disable anti-pop features */ + snd_soc_component_update_bits(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + break; + + default: + break; + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 pre_div:1; + u32 n:4; + u32 k:24; +}; + +static bool is_pll_freq_available(unsigned int source, unsigned int target) +{ + unsigned int Ndiv; + + if (source == 0 || target == 0) + return false; + + /* Scale up target to PLL operating frequency */ + target *= 4; + Ndiv = target / source; + + if ((Ndiv < 6) || (Ndiv > 12)) + return false; + + return true; +} + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later + */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static int pll_factors(unsigned int source, unsigned int target, + struct _pll_div *pll_div) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); + + /* Scale up target to PLL operating frequency */ + target *= 4; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->pre_div = 1; + Ndiv = target / source; + } else + pll_div->pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) { + pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; + + pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", + pll_div->n, pll_div->k, pll_div->pre_div); + + return 0; +} + +static int wm8960_set_pll(struct snd_soc_component *component, + unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + static struct _pll_div pll_div; + int ret; + + if (freq_in && freq_out) { + ret = pll_factors(freq_in, freq_out, &pll_div); + if (ret != 0) + return ret; + } + + /* Disable the PLL: even if we are changing the frequency the + * PLL needs to be disabled while we do so. + */ + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0); + snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0); + + if (!freq_in || !freq_out) + return 0; + + reg = snd_soc_component_read32(component, WM8960_PLL1) & ~0x3f; + reg |= pll_div.pre_div << 4; + reg |= pll_div.n; + + if (pll_div.k) { + reg |= 0x20; + + snd_soc_component_write(component, WM8960_PLL2, (pll_div.k >> 16) & 0xff); + snd_soc_component_write(component, WM8960_PLL3, (pll_div.k >> 8) & 0xff); + snd_soc_component_write(component, WM8960_PLL4, pll_div.k & 0xff); + } + snd_soc_component_write(component, WM8960_PLL1, reg); + + /* Turn it on */ + snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0x1); + msleep(250); + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0x1); + + return 0; +} + +static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + wm8960->freq_in = freq_in; + + if (pll_id == WM8960_SYSCLK_AUTO) + return 0; + + if (is_pll_freq_available(freq_in, freq_out)) + return -EINVAL; + + return wm8960_set_pll(component, freq_in, freq_out); +} + +static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + switch (div_id) { + case WM8960_SYSCLKDIV: + reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1f9; + snd_soc_component_write(component, WM8960_CLOCK1, reg | div); + break; + case WM8960_DACDIV: + reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1c7; + snd_soc_component_write(component, WM8960_CLOCK1, reg | div); + break; + case WM8960_OPCLKDIV: + reg = snd_soc_component_read32(component, WM8960_PLL1) & 0x03f; + snd_soc_component_write(component, WM8960_PLL1, reg | div); + break; + case WM8960_DCLKDIV: + reg = snd_soc_component_read32(component, WM8960_CLOCK2) & 0x03f; + snd_soc_component_write(component, WM8960_CLOCK2, reg | div); + break; + case WM8960_TOCLKSEL: + reg = snd_soc_component_read32(component, WM8960_ADDCTL1) & 0x1fd; + snd_soc_component_write(component, WM8960_ADDCTL1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8960_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + return wm8960->set_bias_level(component, level); +} + +static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM8960_SYSCLK_MCLK: + snd_soc_component_update_bits(component, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_MCLK); + break; + case WM8960_SYSCLK_PLL: + snd_soc_component_update_bits(component, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_PLL); + break; + case WM8960_SYSCLK_AUTO: + break; + default: + return -EINVAL; + } + + wm8960->sysclk = freq; + wm8960->clk_id = clk_id; + + return 0; +} + +#define RPMSG_RATES SNDRV_PCM_RATE_8000_48000 + +#define RPMSG_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops rpmsg_wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .hw_free = wm8960_hw_free, + .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, +}; + +struct snd_soc_dai_driver rpmsg_wm8960_codec_dai = { + .name = "rpmsg-wm8960-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RPMSG_RATES, + .formats = RPMSG_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RPMSG_RATES, + .formats = RPMSG_FORMATS, + }, + .ops = &rpmsg_wm8960_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL(rpmsg_wm8960_codec_dai); + +static int rpmsg_wm8960_probe(struct snd_soc_component *component) +{ + struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + struct wm8960_data *pdata = &wm8960->pdata; + + if (pdata->capless) + wm8960->set_bias_level = wm8960_set_bias_level_capless; + else + wm8960->set_bias_level = wm8960_set_bias_level_out3; + + snd_soc_add_component_controls(component, wm8960_snd_controls, + ARRAY_SIZE(wm8960_snd_controls)); + wm8960_add_widgets(component); + + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "HP_L"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "HP_R"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "Capture"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "MICB"); + + return 0; +} + +static int rpmsg_wm8960_read(void *context, unsigned int reg, unsigned int *val) +{ + struct rpmsg_wm8960_priv *wm8960 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = wm8960->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg; + int err, reg_val; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = wm8960->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->header.cmd = GET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info); + reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data; + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + *val = reg_val; + return 0; +} + +static int rpmsg_wm8960_write(void *context, unsigned int reg, unsigned int val) +{ + struct rpmsg_wm8960_priv *wm8960 = context; + struct fsl_rpmsg_i2s *rpmsg_i2s = wm8960->rpmsg_i2s; + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg; + int err; + + mutex_lock(&i2s_info->i2c_lock); + rpmsg->param.audioindex = wm8960->audioindex; + rpmsg->param.buffer_addr = reg; + rpmsg->param.buffer_size = val; + rpmsg->header.cmd = SET_CODEC_VALUE; + err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info); + mutex_unlock(&i2s_info->i2c_lock); + if (err) + return -EIO; + + return 0; +} + +const struct snd_soc_component_driver rpmsg_wm8960_component = { + .probe = rpmsg_wm8960_probe, + .set_bias_level = wm8960_set_bias_level, + .suspend_bias_off = 1, +}; +EXPORT_SYMBOL(rpmsg_wm8960_component); + +struct regmap_config rpmsg_wm8960_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8960_PLL4, + + .reg_defaults = wm8960_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8960_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8960_volatile, + .reg_read = rpmsg_wm8960_read, + .reg_write = rpmsg_wm8960_write, +}; +EXPORT_SYMBOL(rpmsg_wm8960_regmap); + +#ifdef CONFIG_PM +static int wm8960_runtime_resume(struct device *dev) +{ + struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + return 0; +} + +static int wm8960_runtime_suspend(struct device *dev) +{ + struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev); + + clk_disable_unprepare(wm8960->mclk); + + return 0; +} +#endif + +static const struct dev_pm_ops wm8960_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(wm8960_runtime_suspend, wm8960_runtime_resume, NULL) +}; + +static int rpmsg_wm8960_codec_probe(struct platform_device *pdev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent); + struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data; + struct rpmsg_wm8960_priv *wm8960; + int ret; + int repeat_reset = 10; + + wm8960 = devm_kzalloc(&pdev->dev, sizeof(struct rpmsg_wm8960_priv), + GFP_KERNEL); + if (wm8960 == NULL) + return -ENOMEM; + + wm8960->rpmsg_i2s = rpmsg_i2s; + + wm8960->mclk = devm_clk_get(pdev->dev.parent, "mclk"); + if (IS_ERR(wm8960->mclk)) { + if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + wm8960->mclk = NULL; + } + + dev_set_drvdata(&pdev->dev, wm8960); + + wm8960->regmap = devm_regmap_init(&pdev->dev, NULL, wm8960, &rpmsg_wm8960_regmap); + if (IS_ERR(wm8960->regmap)) + return PTR_ERR(wm8960->regmap); + + if (pdata) { + wm8960->pdata.shared_lrclk = pdata->shared_lrclk; + wm8960->pdata.capless = pdata->capless; + wm8960->audioindex = pdata->audioindex; + } + + if (wm8960->mclk) { + do { + ret = wm8960_reset(wm8960->regmap); + repeat_reset--; + } while (repeat_reset > 0 && ret != 0); + + if (ret != 0) { + dev_err(&pdev->dev, "Failed to issue reset\n"); + return ret; + } + + if (wm8960->pdata.shared_lrclk) { + ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, + 0x4, 0x4); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to enable LRCM: %d\n", + ret); + return ret; + } + } + } + + /* Latch the update bits */ + regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100); + + pm_runtime_enable(&pdev->dev); + + if (!wm8960->mclk) + rpmsg_wm8960_codec_dai.ops = NULL; + + ret = devm_snd_soc_register_component(&pdev->dev, + &rpmsg_wm8960_component, &rpmsg_wm8960_codec_dai, 1); + + return ret; +} + +static int rpmsg_wm8960_codec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver rpmsg_wm8960_codec_driver = { + .driver = { + .name = RPMSG_CODEC_DRV_NAME_WM8960, + .pm = &wm8960_pm, + }, + .probe = rpmsg_wm8960_codec_probe, + .remove = rpmsg_wm8960_codec_remove, +}; + +module_platform_driver(rpmsg_wm8960_codec_driver); + +MODULE_DESCRIPTION("rpmsg wm8960 Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rpmsg_wm8960.h b/sound/soc/codecs/rpmsg_wm8960.h new file mode 100644 index 000000000000..6ecb181e3b08 --- /dev/null +++ b/sound/soc/codecs/rpmsg_wm8960.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Audio driver for RPMSG WM8960 + * + * Copyright 2007-11 Wolfson Microelectronics, plc + * Copyright 2020 NXP + */ + +#ifndef _RPMSG_WM8960_H +#define _RPMSG_WM8960_H + +/* R25 - Power 1 */ +#define WM8960_VMID_MASK 0x180 +#define WM8960_VREF 0x40 + +/* R26 - Power 2 */ +#define WM8960_PWR2_LOUT1 0x40 +#define WM8960_PWR2_ROUT1 0x20 +#define WM8960_PWR2_OUT3 0x02 + +/* R28 - Anti-pop 1 */ +#define WM8960_POBCTRL 0x80 +#define WM8960_BUFDCOPEN 0x10 +#define WM8960_BUFIOEN 0x08 +#define WM8960_SOFT_ST 0x04 +#define WM8960_HPSTBY 0x01 + +/* R29 - Anti-pop 2 */ +#define WM8960_DISOP 0x40 +#define WM8960_DRES_MASK 0x30 + +#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0) + +struct rpmsg_wm8960_priv { + struct clk *mclk; + struct regmap *regmap; + int (*set_bias_level)(struct snd_soc_component *, + enum snd_soc_bias_level level); + struct snd_soc_dapm_widget *lout1; + struct snd_soc_dapm_widget *rout1; + struct snd_soc_dapm_widget *out3; + bool deemph; + int lrclk; + int bclk; + int sysclk; + int clk_id; + int freq_in; + bool is_stream_in_use[2]; + struct wm8960_data pdata; + struct fsl_rpmsg_i2s *rpmsg_i2s; + int audioindex; +}; + +#endif diff --git a/sound/soc/codecs/rpmsg_wm8960_i2c.c b/sound/soc/codecs/rpmsg_wm8960_i2c.c new file mode 100644 index 000000000000..5449e037beea --- /dev/null +++ b/sound/soc/codecs/rpmsg_wm8960_i2c.c @@ -0,0 +1,155 @@ +/* + * Copyright 2020 NXP + * + * Copyright 2007-11 Wolfson Microelectronics, plc + * + * Author: Liam Girdwood + * + * 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/clk.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/wm8960.h> +#include "../fsl/fsl_rpmsg_i2s.h" +#include "wm8960.h" +#include "rpmsg_wm8960.h" + +extern struct regmap_config rpmsg_wm8960_regmap; +extern const struct snd_soc_component_driver rpmsg_wm8960_component; +extern struct snd_soc_dai_driver rpmsg_wm8960_codec_dai; + +static void rpmsg_wm8960_set_pdata_from_of(struct i2c_client *i2c, + struct wm8960_data *pdata) +{ + const struct device_node *np = i2c->dev.of_node; + + if (of_property_read_bool(np, "wlf,capless")) + pdata->capless = true; + + if (of_property_read_bool(np, "wlf,shared-lrclk")) + pdata->shared_lrclk = true; +} + +static int rpmsg_wm8960_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); + struct rpmsg_wm8960_priv *wm8960; + int ret; + int repeat_reset = 10; + + wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct rpmsg_wm8960_priv), + GFP_KERNEL); + if (wm8960 == NULL) + return -ENOMEM; + + wm8960->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(wm8960->mclk)) { + if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } + + rpmsg_wm8960_regmap.reg_read = NULL; + rpmsg_wm8960_regmap.reg_write = NULL; + + wm8960->regmap = devm_regmap_init_i2c(i2c, &rpmsg_wm8960_regmap); + if (IS_ERR(wm8960->regmap)) + return PTR_ERR(wm8960->regmap); + + if (pdata) + memcpy(&wm8960->pdata, pdata, sizeof(struct wm8960_data)); + else if (i2c->dev.of_node) + rpmsg_wm8960_set_pdata_from_of(i2c, &wm8960->pdata); + + i2c_set_clientdata(i2c, wm8960); + + do { + ret = wm8960_reset(wm8960->regmap); + repeat_reset--; + } while (repeat_reset > 0 && ret != 0); + + if (ret != 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + return ret; + } + + if (wm8960->pdata.shared_lrclk) { + ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, + 0x4, 0x4); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable LRCM: %d\n", + ret); + return ret; + } + } + + /* Latch the update bits */ + regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100); + + /* + * codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame + * clock for ADCs and DACs + */ + regmap_update_bits(wm8960->regmap, WM8960_IFACE2, 1<<6, 1<<6); + + ret = devm_snd_soc_register_component(&i2c->dev, + &rpmsg_wm8960_component, &rpmsg_wm8960_codec_dai, 1); + + return ret; +} + +static int rpmsg_wm8960_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id rpmsg_wm8960_i2c_id[] = { + { "wm8960", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rpmsg_wm8960_i2c_id); + +static const struct of_device_id rpmsg_wm8960_of_match[] = { + { .compatible = "wlf,wm8960,lpa", }, + { } +}; +MODULE_DEVICE_TABLE(of, rpmsg_wm8960_of_match); + +static struct i2c_driver rpmsg_wm8960_i2c_driver = { + .driver = { + .name = RPMSG_CODEC_DRV_NAME_WM8960, + .of_match_table = rpmsg_wm8960_of_match, + }, + .probe = rpmsg_wm8960_i2c_probe, + .remove = rpmsg_wm8960_i2c_remove, + .id_table = rpmsg_wm8960_i2c_id, +}; + +module_i2c_driver(rpmsg_wm8960_i2c_driver); +MODULE_DESCRIPTION("rpmsg wm8960 I2C Codec Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c index 8d88db9c11a6..8924aeb6bed4 100644 --- a/sound/soc/codecs/si476x.c +++ b/sound/soc/codecs/si476x.c @@ -199,9 +199,28 @@ out: return err; } +static int si476x_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev); + + if (!si476x_core_is_powered_up(core)) + si476x_core_set_power_state(core, SI476X_POWER_UP_FULL); + return 0; +} + +static void si476x_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev); + + if (si476x_core_is_powered_up(core)) + si476x_core_set_power_state(core, SI476X_POWER_DOWN); +} + static const struct snd_soc_dai_ops si476x_dai_ops = { .hw_params = si476x_codec_hw_params, .set_fmt = si476x_codec_set_dai_fmt, + .startup = si476x_codec_startup, + .shutdown = si476x_codec_shutdown, }; static struct snd_soc_dai_driver si476x_dai = { diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index f34637afee51..472c2fff34a8 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -1108,8 +1108,10 @@ static int twl6040_probe(struct snd_soc_component *component) priv->component = component; priv->plug_irq = platform_get_irq(pdev, 0); - if (priv->plug_irq < 0) + if (priv->plug_irq < 0) { + dev_err(component->dev, "invalid irq: %d\n", priv->plug_irq); return priv->plug_irq; + } INIT_DELAYED_WORK(&priv->hs_jack.work, twl6040_accessory_work); diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c index 91e3d1570c45..256c09057b07 100644 --- a/sound/soc/codecs/wm8524.c +++ b/sound/soc/codecs/wm8524.c @@ -61,6 +61,7 @@ static int wm8524_startup(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = substream->private_data; /* The set of sample rates that can be supported depends on the * MCLK supplied to the CODEC - enforce this. @@ -71,9 +72,10 @@ static int wm8524_startup(struct snd_pcm_substream *substream, return -EINVAL; } - snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, - &wm8524->rate_constraint); + if (!rtd->dai_link->be_hw_params_fixup) + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8524->rate_constraint); gpiod_set_value_cansleep(wm8524->mute, 1); @@ -159,7 +161,8 @@ static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream) #define WM8524_RATES SNDRV_PCM_RATE_8000_192000 -#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops wm8524_dai_ops = { .startup = wm8524_startup, @@ -225,7 +228,8 @@ static int wm8524_codec_probe(struct platform_device *pdev) wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW); if (IS_ERR(wm8524->mute)) { ret = PTR_ERR(wm8524->mute); - dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret); return ret; } diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 512f8899dcbb..cf338ad9cddd 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -608,10 +608,6 @@ static const int bclk_divs[] = { * - lrclk = sysclk / dac_divs * - 10 * bclk = sysclk / bclk_divs * - * If we cannot find an exact match for (sysclk, lrclk, bclk) - * triplet, we relax the bclk such that bclk is chosen as the - * closest available frequency greater than expected bclk. - * * @wm8960_priv: wm8960 codec private data * @mclk: MCLK used to derive sysclk * @sysclk_idx: sysclk_divs index for found sysclk @@ -629,7 +625,7 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, { int sysclk, bclk, lrclk; int i, j, k; - int diff, closest = mclk; + int diff; /* marker for no match */ *bclk_idx = -1; @@ -653,12 +649,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, *bclk_idx = k; break; } - if (diff > 0 && closest > diff) { - *sysclk_idx = i; - *dac_idx = j; - *bclk_idx = k; - closest = diff; - } } if (k != ARRAY_SIZE(bclk_divs)) break; @@ -676,10 +666,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, * - freq_out = sysclk * sysclk_divs * - 10 * sysclk = bclk * bclk_divs * - * If we cannot find an exact match for (sysclk, lrclk, bclk) - * triplet, we relax the bclk such that bclk is chosen as the - * closest available frequency greater than expected bclk. - * * @component: component structure * @freq_in: input frequency used to derive freq out via PLL * @sysclk_idx: sysclk_divs index for found sysclk @@ -697,12 +683,11 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, { struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); int sysclk, bclk, lrclk, freq_out; - int diff, closest, best_freq_out; + int diff, best_freq_out; int i, j, k; bclk = wm8960->bclk; lrclk = wm8960->lrclk; - closest = freq_in; best_freq_out = -EINVAL; *sysclk_idx = *dac_idx = *bclk_idx = -1; @@ -731,13 +716,6 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, *bclk_idx = k; return freq_out; } - if (diff > 0 && closest > diff) { - *sysclk_idx = i; - *dac_idx = j; - *bclk_idx = k; - closest = diff; - best_freq_out = freq_out; - } } } } @@ -1132,11 +1110,6 @@ static bool is_pll_freq_available(unsigned int source, unsigned int target) target *= 4; Ndiv = target / source; - if (Ndiv < 6) { - source >>= 1; - Ndiv = target / source; - } - if ((Ndiv < 6) || (Ndiv > 12)) return false; @@ -1247,6 +1220,9 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, if (pll_id == WM8960_SYSCLK_AUTO) return 0; + if (is_pll_freq_available(freq_in, freq_out)) + return -EINVAL; + return wm8960_set_pll(component, freq_in, freq_out); } @@ -1410,6 +1386,7 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); struct wm8960_priv *wm8960; int ret; + int repeat_reset = 10; wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv), GFP_KERNEL); @@ -1431,7 +1408,11 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, else if (i2c->dev.of_node) wm8960_set_pdata_from_of(i2c, &wm8960->pdata); - ret = wm8960_reset(wm8960->regmap); + do { + ret = wm8960_reset(wm8960->regmap); + repeat_reset--; + } while (repeat_reset > 0 && ret != 0); + if (ret != 0) { dev_err(&i2c->dev, "Failed to issue reset\n"); return ret; diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index d9d59f45833f..0b2f84f5cec9 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3,6 +3,7 @@ * wm8962.c -- WM8962 ALSA SoC Audio driver * * Copyright 2010-2 Wolfson Microelectronics plc + * Copyright 2017 NXP * * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> */ @@ -82,6 +83,7 @@ struct wm8962_priv { #endif int irq; + u32 cache_clocking2_reg; }; /* We can't use the same notifier block for more than one supply and @@ -1777,8 +1779,11 @@ SND_SOC_BYTES("HD Bass Coefficients", WM8962_HDBASS_AI_1, 30), SOC_DOUBLE("ALC Switch", WM8962_ALC1, WM8962_ALCL_ENA_SHIFT, WM8962_ALCR_ENA_SHIFT, 1, 0), -SND_SOC_BYTES_MASK("ALC Coefficients", WM8962_ALC1, 4, +SND_SOC_BYTES_MASK("ALC1", WM8962_ALC1, 1, WM8962_ALCL_ENA_MASK | WM8962_ALCR_ENA_MASK), +SND_SOC_BYTES("ALC2", WM8962_ALC2, 1), +SND_SOC_BYTES("ALC3", WM8962_ALC3, 1), +SND_SOC_BYTES("Noise Gate", WM8962_NOISE_GATE, 1), }; static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = { @@ -2554,11 +2559,17 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + snd_pcm_format_t sample_format = params_format(params); int i; int aif0 = 0; int adctl3 = 0; - wm8962->bclk = snd_soc_params_to_bclk(params); + if (sample_format == SNDRV_PCM_FORMAT_S20_3LE) + wm8962->bclk = params_rate(params) * + params_channels(params) * + params_physical_width(params); + else + wm8962->bclk = snd_soc_params_to_bclk(params); if (params_channels(params) == 1) wm8962->bclk *= 2; @@ -3813,6 +3824,10 @@ static int wm8962_runtime_resume(struct device *dev) regcache_sync(wm8962->regmap); + regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2, + WM8962_SYSCLK_SRC_MASK, + wm8962->cache_clocking2_reg); + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA); @@ -3842,6 +3857,9 @@ static int wm8962_runtime_suspend(struct device *dev) WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, 0); + regmap_read(wm8962->regmap, WM8962_CLOCKING2, + &wm8962->cache_clocking2_reg); + regcache_cache_only(wm8962->regmap, true); regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), @@ -3854,6 +3872,7 @@ static int wm8962_runtime_suspend(struct device *dev) #endif static const struct dev_pm_ops wm8962_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) SET_RUNTIME_PM_OPS(wm8962_runtime_suspend, wm8962_runtime_resume, NULL) }; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index aa99c008a925..616b31ee2363 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -74,13 +74,83 @@ config SND_SOC_FSL_MICFIL Say Y if you want to add Pulse Density Modulation microphone interface (MICFIL) support for NXP. +config SND_SOC_FSL_EASRC + tristate "Enhanced ASRC module support" + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Enhanced ASRC support for NXP. The ASRC is + a digital module that converts audio from a source sample rate to a + destination sample rate. It is a new design module compare with the + old ASRC. + +config SND_SOC_FSL_DSP + tristate "dsp module support" + select SND_SOC_COMPRESS + select SND_SOC_FSL_DSP_CPU + select SND_SOC_FSL_DSP_AUDIOMIX + help + Say Y if you want to add hifi 4 support for the Freescale CPUs. + which is a DSP core for audio processing. + +config SND_SOC_FSL_RPMSG_I2S + tristate "I2S base on the RPMSG support" + depends on RPMSG + help + Say Y if you want to add rpmsg i2s support for the Freescale CPUs. + which is depends on the rpmsg. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_AUD2HTX + tristate "AUDIO TO HDMI TX module support" + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add AUDIO TO HDMI TX support for NXP. + +config SND_SOC_FSL_DSP_CPU + tristate + +config SND_SOC_FSL_DSP_AUDIOMIX + tristate "Audio MIX DSP helper" + depends on MFD_IMX_AUDIOMIX + help + Say Y if you want to add Audio MIX helper for DSP + +config SND_SOC_FSL_XCVR + tristate "NXP Audio Transceiver (XCVR) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Audio Transceiver (XCVR) support for NXP. + +config SND_SOC_FSL_ESAI_CLIENT + tristate "NXP ESAI CLIENT module support" + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + help + Say Y if you want to add ESAI CLIENT module support for NXP. + config SND_SOC_FSL_UTILS tristate +config SND_SOC_FSL_HDMI + tristate + config SND_SOC_IMX_PCM_DMA tristate select SND_SOC_GENERIC_DMAENGINE_PCM +config SND_SOC_IMX_PCM_RPMSG + tristate + depends on RPMSG + select SND_SOC_GENERIC_DMAENGINE_PCM + +config SND_SOC_IMX_I2S_RPMSG + tristate + depends on SND_SOC_FSL_RPMSG_I2S + config SND_SOC_IMX_AUDMUX tristate "Digital Audio Mux module support" help @@ -98,7 +168,7 @@ config SND_POWERPC_SOC config SND_IMX_SOC tristate "SoC Audio for Freescale i.MX CPUs" - depends on ARCH_MXC || COMPILE_TEST + depends on ARCH_MXC || ARCH_MXC_ARM64 || COMPILE_TEST help Say Y or M if you want to add support for codecs attached to the i.MX CPUs. @@ -201,6 +271,11 @@ config SND_SOC_IMX_SSI tristate select SND_SOC_FSL_UTILS +config SND_SOC_IMX_HDMI_DMA + bool + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_IMX_PCM_DMA + comment "SoC Audio support for Freescale i.MX boards:" config SND_MXC_SOC_WM1133_EV1 @@ -249,6 +324,151 @@ config SND_SOC_EUKREA_TLV320 Enable I2S based access to the TLV320AIC23B codec attached to the SSI interface +config SND_SOC_IMX_AK4458 + tristate "SoC Audio support for i.MX boards with AK4458" + depends on OF && I2C + select SND_SOC_AK4458_I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK4458 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK4458 DAC. + +config SND_SOC_IMX_AK5558 + tristate "SoC Audio support for i.MX boards with AK5558" + depends on OF && I2C + select SND_SOC_AK5558 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK5558 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK5558 ADC. + +config SND_SOC_IMX_AK4497 + tristate "SoC Audio support for i.MX boards with AK4497" + depends on OF && I2C + select SND_SOC_AK4458 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with AK4497 + Say Y if you want to add support for SoC audio on an i.MX board with + an AK4497 DAC. + +config SND_SOC_IMX_WM8960 + tristate "SoC Audio support for i.MX boards with wm8960" + depends on OF && I2C + select SND_SOC_WM8960 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8960 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8960 codec. + +config SND_SOC_IMX_WM8524 + tristate "SoC Audio support for i.MX boards with wm8524" + depends on OF && I2C + select SND_SOC_WM8524 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8524 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8524 codec. + +config SND_SOC_IMX_SII902X + tristate "SoC Audio support for i.MX boards with sii902x" + depends on OF && I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with SII902X + Say Y if you want to add support for SoC audio on an i.MX board with + a sii902x. + +config SND_SOC_IMX_WM8958 + tristate "SoC Audio support for i.MX boards with wm8958" + depends on OF && I2C + select MFD_WM8994 + select SND_SOC_WM8994 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8958 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8958 codec. + +config SND_SOC_IMX_CS42888 + tristate "SoC Audio support for i.MX boards with cs42888" + depends on OF && I2C + select SND_SOC_CS42XX8_I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_ESAI + select SND_SOC_FSL_ASRC + select SND_SOC_FSL_UTILS + select SND_SOC_FSL_ESAI_CLIENT + help + SoC Audio support for i.MX boards with cs42888 + Say Y if you want to add support for SoC audio on an i.MX board with + a cs42888 codec. + +config SND_SOC_IMX_WM8962 + tristate "SoC Audio support for i.MX boards with wm8962" + depends on OF && I2C && INPUT + select SND_SOC_WM8962 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + select SND_KCTL_JACK + help + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8962 codec. + +config SND_SOC_IMX_WM8962_ANDROID + tristate "SoC Audio support for i.MX boards with wm8962 in android" + depends on SND_SOC_IMX_WM8962=y + help + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8962 codec in android. + +config SND_SOC_IMX_MICFIL + tristate "SoC Audio support for i.MX boards with micfil" + depends on OF && I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_MICFIL + help + Soc Audio support for i.MX boards with micfil + Say Y if you want to add support for SoC audio on + an i.MX board with micfil. + +config SND_SOC_IMX_RPMSG + tristate "SoC Audio support for i.MX boards with rpmsg" + depends on RPMSG + select SND_SOC_IMX_PCM_RPMSG + select SND_SOC_IMX_I2S_RPMSG + select SND_SOC_FSL_RPMSG_I2S + select SND_SOC_RPMSG_WM8960 + select SND_SOC_RPMSG_AK4497 + select SND_SOC_RPMSG_CS42XX8 + help + SoC Audio support for i.MX boards with rpmsg. + There should be rpmsg devices defined in other core + Say Y if you want to add support for SoC audio on an i.MX board with + a rpmsg devices. + config SND_SOC_IMX_ES8328 tristate "SoC Audio support for i.MX boards with the ES8328 codec" depends on OF && (I2C || SPI) @@ -272,6 +492,14 @@ config SND_SOC_IMX_SGTL5000 Say Y if you want to add support for SoC audio on an i.MX board with a sgtl5000 codec. +config SND_SOC_IMX_MQS + tristate "SoC Audio support for i.MX boards with MQS" + depends on OF + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_MQS + select SND_SOC_FSL_UTILS + config SND_SOC_IMX_SPDIF tristate "SoC Audio support for i.MX boards with S/PDIF" select SND_SOC_IMX_PCM_DMA @@ -302,7 +530,7 @@ config SND_SOC_FSL_ASOC_CARD help ALSA SoC Audio support with ASRC feature for Freescale SoCs that have ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888, - CS4271, CS4272 and SGTL5000. + CS4271, CS4272, and SGTL5000. Say Y if you want to add support for Freescale Generic ASoC Sound Card. config SND_SOC_IMX_AUDMIX @@ -314,6 +542,80 @@ config SND_SOC_IMX_AUDMIX Say Y if you want to add support for SoC audio on an i.MX board with an Audio Mixer. +config SND_SOC_IMX_PDM_MIC + tristate "SoC Audio support for i.MX boards with PDM mic on SAI" + depends on OF + select SND_SOC_IMX_PDM_DMA + select SND_SOC_FSL_SAI + help + SoC Audio support for i.MX boards with PDM microphones on SAI + Say Y if you want to add support for SoC Audio support for i.MX boards + with PDM microphones on SAI. + +config SND_SOC_IMX_DSP + tristate "SoC Audio support for i.MX boards with DSP port" + select SND_SOC_FSL_DSP + select SND_SOC_COMPRESS + help + SoC Audio support for i.MX boards with DSP audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX DSP. + +config SND_SOC_IMX_SI476X + tristate "SoC Audio support for i.MX boards with si476x" + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_SI476X + help + SoC Audio support for i.MX boards with SI476x + Say Y if you want to add support for Soc audio for the AMFM Tuner chip + SI476x module. + +config SND_SOC_IMX_HDMI + tristate "SoC Audio support for i.MX boards with HDMI port" + depends on MFD_MXC_HDMI + select SND_SOC_IMX_HDMI_DMA + select SND_SOC_FSL_HDMI + select SND_SOC_HDMI_CODEC + help + SoC Audio support for i.MX boards with HDMI audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX HDMI. + +config SND_SOC_IMX_CDNHDMI + tristate "SoC Audio support for i.MX boards with CDN HDMI port" + depends on DRM_IMX_CDNS_MHDP + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_AUD2HTX + select SND_SOC_HDMI_CODEC + help + SoC Audio support for i.MX boards with CDN HDMI audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX CDN HDMI. + +config SND_SOC_IMX_XCVR + tristate "SoC Audio support for i.MX boards with XCVR" + select SND_SOC_FSL_XCVR + help + SoC Audio support for i.MX boards with Audio Transceiver (XCVR) + Say Y if you want to add support for SoC audio on an i.MX board with + an Audio Transceiver (XCVR). + +config SND_SOC_IMX_PCM512X + tristate "SoC Audio support for i.MX boards with PCM512x" + depends on OF && I2C + select SND_SOC_PCM512x_I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + help + SoC Audio support for i.MX boards with pcm512x + Say Y if you want to add support for SoC audio on an i.MX board with + an pcm512x codec. + endif # SND_IMX_SOC endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index c0dd04422fe9..d463e7e81818 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -13,27 +13,48 @@ obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o # Freescale SSI/DMA/SAI/SPDIF Support snd-soc-fsl-audmix-objs := fsl_audmix.o -snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o +snd-soc-fsl-dsp-audiomix-objs := fsl_dsp_audiomix.o snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o -snd-soc-fsl-sai-objs := fsl_sai.o +snd-soc-fsl-dsp-objs := fsl_dsp.o fsl_dsp_proxy.o fsl_dsp_pool.o \ + fsl_dsp_library_load.o fsl_dsp_xaf_api.o \ + fsl_dsp_platform_compress.o +snd-soc-fsl-dsp-cpu-objs := fsl_dsp_cpu.o +snd-soc-fsl-sai-objs := fsl_sai.o fsl_sai_sysfs.o snd-soc-fsl-ssi-y := fsl_ssi.o snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o snd-soc-fsl-spdif-objs := fsl_spdif.o -snd-soc-fsl-esai-objs := fsl_esai.o +snd-soc-fsl-esai-objs := fsl_esai.o fsl_esai_mix.o snd-soc-fsl-micfil-objs := fsl_micfil.o snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o +snd-soc-fsl-easrc-objs := fsl_easrc.o fsl_easrc_dma.o +snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o +snd-soc-fsl-xcvr-objs := fsl_xcvr.o fsl_xcvr_sysfs.o +snd-soc-fsl-esai-client-objs := fsl_esai_client.o obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o +snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o +snd-soc-fsl-rpmsg-i2s-objs := fsl_rpmsg_i2s.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o +snd-soc-fsl-hdmi-objs := fsl_hdmi.o obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o +obj-$(CONFIG_SND_SOC_FSL_DSP) += snd-soc-fsl-dsp.o +obj-$(CONFIG_SND_SOC_FSL_DSP_CPU) += snd-soc-fsl-dsp-cpu.o +obj-$(CONFIG_SND_SOC_FSL_DSP_AUDIOMIX) += snd-soc-fsl-dsp-audiomix.o obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o obj-$(CONFIG_SND_SOC_FSL_MICFIL) += snd-soc-fsl-micfil.o obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o +obj-$(CONFIG_SND_SOC_FSL_HDMI) += snd-soc-fsl-hdmi.o obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o +obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o +obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o +obj-$(CONFIG_SND_SOC_FSL_RPMSG_I2S) += snd-soc-fsl-rpmsg-i2s.o +obj-$(CONFIG_SND_SOC_FSL_AUD2HTX) += snd-soc-fsl-aud2htx.o +obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o +obj-$(CONFIG_SND_SOC_FSL_ESAI_CLIENT) += snd-soc-fsl-esai-client.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o @@ -51,7 +72,11 @@ obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o +obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o imx-pcm-dma-v2.o obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o +obj-$(CONFIG_SND_SOC_IMX_HDMI_DMA) += imx-hdmi-dma.o hdmi_pcm.o +obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o +obj-$(CONFIG_SND_SOC_IMX_I2S_RPMSG) += imx-i2s-rpmsg.o # i.MX Machine Support snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o @@ -59,17 +84,57 @@ snd-soc-phycore-ac97-objs := phycore-ac97.o snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o snd-soc-imx-es8328-objs := imx-es8328.o +snd-soc-imx-cs42888-objs := imx-cs42888.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o +snd-soc-imx-wm8958-objs := imx-wm8958.o +snd-soc-imx-wm8960-objs := imx-wm8960.o +snd-soc-imx-wm8524-objs := imx-wm8524.o +snd-soc-imx-wm8962-objs := imx-wm8962.o +snd-soc-imx-sii902x-objs := imx-sii902x.o snd-soc-imx-spdif-objs := imx-spdif.o snd-soc-imx-mc13783-objs := imx-mc13783.o snd-soc-imx-audmix-objs := imx-audmix.o +snd-soc-imx-mqs-objs := imx-mqs.o +snd-soc-imx-pdm-objs := imx-pdm.o +snd-soc-imx-ak4458-objs := imx-ak4458.o +snd-soc-imx-ak5558-objs := imx-ak5558.o +snd-soc-imx-ak4497-objs := imx-ak4497.o +snd-soc-imx-micfil-objs := imx-micfil.o +snd-soc-imx-dsp-objs := imx-dsp.o +snd-soc-imx-si476x-objs := imx-si476x.o +snd-soc-imx-hdmi-objs := imx-hdmi.o +snd-soc-imx-cdnhdmi-objs := imx-cdnhdmi.o +snd-soc-imx-rpmsg-objs := imx-rpmsg.o +snd-soc-imx-xcvr-objs := imx-xcvr.o +snd-soc-imx-pcm512x-objs := imx-pcm512x.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o +obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o +obj-${CONFIG_SND_SOC_IMX_WM8958} += snd-soc-imx-wm8958.o +obj-$(CONFIG_SND_SOC_IMX_WM8960) += snd-soc-imx-wm8960.o +obj-$(CONFIG_SND_SOC_IMX_WM8524) += snd-soc-imx-wm8524.o +obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o +obj-$(CONFIG_SND_SOC_IMX_SII902X) += snd-soc-imx-sii902x.o +obj-$(CONFIG_SND_SOC_IMX_RPMSG) += snd-soc-imx-rpmsg.o obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o +obj-$(CONFIG_SND_SOC_IMX_MQS) += snd-soc-imx-mqs.o +obj-$(CONFIG_SND_SOC_IMX_PDM_MIC) += snd-soc-imx-pdm.o +obj-$(CONFIG_SND_SOC_IMX_AK4458) += snd-soc-imx-ak4458.o +obj-$(CONFIG_SND_SOC_IMX_AK5558) += snd-soc-imx-ak5558.o +obj-$(CONFIG_SND_SOC_IMX_AK4497) += snd-soc-imx-ak4497.o +obj-$(CONFIG_SND_SOC_IMX_MICFIL) += snd-soc-imx-micfil.o +obj-$(CONFIG_SND_SOC_IMX_DSP) += snd-soc-imx-dsp.o +obj-$(CONFIG_SND_SOC_IMX_SI476X) += snd-soc-imx-si476x.o +obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o +obj-$(CONFIG_SND_SOC_IMX_CDNHDMI) += snd-soc-imx-cdnhdmi.o +obj-$(CONFIG_SND_SOC_IMX_XCVR) += snd-soc-imx-xcvr.o +obj-$(CONFIG_SND_SOC_IMX_PCM512X) += snd-soc-imx-pcm512x.o + +AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index cfa40ef6b1ca..6a93fd929563 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -2,7 +2,8 @@ // // Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver // -// Copyright (C) 2014 Freescale Semiconductor, Inc. +// Copyright (C) 2014-2016 Freescale Semiconductor, Inc. +// Copyright 2017 NXP // // Author: Nicolin Chen <nicoleotsuka@gmail.com> @@ -13,6 +14,8 @@ #include <linux/of_platform.h> #include <linux/platform_data/dma-imx.h> #include <linux/pm_runtime.h> +#include <linux/miscdevice.h> +#include <linux/sched/signal.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> @@ -23,6 +26,9 @@ #define pair_err(fmt, ...) \ dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) +#define pair_warn(fmt, ...) \ + dev_warn(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + #define pair_dbg(fmt, ...) \ dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) @@ -41,26 +47,58 @@ static struct snd_pcm_hw_constraint_list fsl_asrc_rate_constraints = { * The following tables map the relationship between asrc_inclk/asrc_outclk in * fsl_asrc.h and the registers of ASRCSR */ +#define CLK_MAP_NUM 48 static unsigned char input_clk_map_imx35[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, }; static unsigned char output_clk_map_imx35[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, }; /* i.MX53 uses the same map for input and output */ static unsigned char input_clk_map_imx53[] = { /* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ 0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, }; static unsigned char output_clk_map_imx53[] = { /* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ 0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, }; -static unsigned char *clk_map[2]; +/* i.MX8 uses the same map for input and output */ +static unsigned char input_clk_map_imx8_0[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; + +static unsigned char output_clk_map_imx8_0[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; + +static unsigned char input_clk_map_imx8_1[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; + +static unsigned char output_clk_map_imx8_1[] = { + 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0, + 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, +}; /** * Select the pre-processing and post-processing options @@ -115,7 +153,7 @@ static void fsl_asrc_sel_proc(int inrate, int outrate, * within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A * while pair A and pair C are comparatively independent. */ -static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) +int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) { enum asrc_pair_index index = ASRC_INVALID_PAIR; struct fsl_asrc *asrc_priv = pair->asrc_priv; @@ -158,7 +196,7 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) * * It clears the resource from asrc_priv and releases the occupied channels. */ -static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) +void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) { struct fsl_asrc *asrc_priv = pair->asrc_priv; enum asrc_pair_index index = pair->index; @@ -260,17 +298,20 @@ static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair, * of struct asrc_config which includes in/output sample rate, width, channel * and clock settings. */ -static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) +static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool p2p_in, bool p2p_out) { struct asrc_config *config = pair->config; struct fsl_asrc *asrc_priv = pair->asrc_priv; enum asrc_pair_index index = pair->index; u32 inrate, outrate, indiv, outdiv; - u32 clk_index[2], div[2]; + u32 clk_index[2], div[2], rem[2]; + u64 clk_rate; int in, out, channels; int pre_proc, post_proc; struct clk *clk; bool ideal; + enum asrc_word_width input_word_width; + enum asrc_word_width output_word_width; if (!config) { pair_err("invalid pair config\n"); @@ -283,9 +324,32 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) return -EINVAL; } - /* Validate output width */ - if (config->output_word_width == ASRC_WIDTH_8_BIT) { - pair_err("does not support 8bit width output\n"); + switch (snd_pcm_format_width(config->input_format)) { + case 8: + input_word_width = ASRC_WIDTH_8_BIT; + break; + case 16: + input_word_width = ASRC_WIDTH_16_BIT; + break; + case 24: + input_word_width = ASRC_WIDTH_24_BIT; + break; + default: + pair_err("does not support this input format, %d\n", + config->input_format); + return -EINVAL; + } + + switch (snd_pcm_format_width(config->output_format)) { + case 16: + output_word_width = ASRC_WIDTH_16_BIT; + break; + case 24: + output_word_width = ASRC_WIDTH_24_BIT; + break; + default: + pair_err("does not support this output format, %d\n", + config->output_format); return -EINVAL; } @@ -320,13 +384,14 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) } /* Validate input and output clock sources */ - clk_index[IN] = clk_map[IN][config->inclk]; - clk_index[OUT] = clk_map[OUT][config->outclk]; + clk_index[IN] = asrc_priv->clk_map[IN][config->inclk]; + clk_index[OUT] = asrc_priv->clk_map[OUT][config->outclk]; /* We only have output clock for ideal ratio mode */ clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]]; - - div[IN] = clk_get_rate(clk) / inrate; + clk_rate = clk_get_rate(clk); + rem[IN] = do_div(clk_rate, inrate); + div[IN] = (u32)clk_rate; if (div[IN] == 0) { pair_err("failed to support input sample rate %dHz by asrck_%x\n", inrate, clk_index[ideal ? OUT : IN]); @@ -335,11 +400,20 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) clk = asrc_priv->asrck_clk[clk_index[OUT]]; - /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */ - if (ideal) - div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE; - else - div[OUT] = clk_get_rate(clk) / outrate; + /* + * When P2P mode, output rate should align with the out samplerate. + * if set too high output rate, there will be lots of Overload. + * When M2M mode, output rate should also need to align with the out + * samplerate, but M2M must use less time to achieve good performance. + */ + clk_rate = clk_get_rate(clk); + if (p2p_out || p2p_in || (!ideal)) { + rem[OUT] = do_div(clk_rate, outrate); + div[OUT] = clk_rate; + } else { + rem[OUT] = do_div(clk_rate, IDEAL_RATIO_RATE); + div[OUT] = clk_rate; + } if (div[OUT] == 0) { pair_err("failed to support output sample rate %dHz by asrck_%x\n", @@ -347,6 +421,23 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) return -EINVAL; } + if (!ideal && (div[IN] > 1024 || div[OUT] > 1024 || + rem[IN] != 0 || rem[OUT] != 0)) { + pair_err("The divider can't be used for non ideal mode\n"); + return -EINVAL; + } + + if (ideal && div[IN] > 1024 && div[OUT] > 1024) { + pair_warn("both divider (%d, %d) are larger than threshold\n", + div[IN], div[OUT]); + } + + if (div[IN] > 1024) + div[IN] = 1024; + + if (div[OUT] > 1024) + div[OUT] = 1024; + /* Set the channel number */ channels = config->channel_num; @@ -361,8 +452,11 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) /* Default setting: Automatic selection for processing mode */ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index)); + + /* Default setting: use internal measured ratio */ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_USRi_MASK(index), 0); + ASRCTR_USRi_MASK(index) | ASRCTR_IDRi_MASK(index), + ASRCTR_USR(index)); /* Set the input and output clock sources */ regmap_update_bits(asrc_priv->regmap, REG_ASRCSR, @@ -383,8 +477,8 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) /* Implement word_width configurations */ regmap_update_bits(asrc_priv->regmap, REG_ASRMCR1(index), ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK, - ASRMCR1i_OW16(config->output_word_width) | - ASRMCR1i_IWD(config->input_word_width)); + ASRMCR1i_OW16(output_word_width) | + ASRMCR1i_IWD(input_word_width)); /* Enable BUFFER STALL */ regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index), @@ -427,7 +521,7 @@ static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) { struct fsl_asrc *asrc_priv = pair->asrc_priv; enum asrc_pair_index index = pair->index; - int reg, retry = 10, i; + int reg, retry = 50, i; /* Enable the current pair */ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, @@ -440,6 +534,9 @@ static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) reg &= ASRCFG_INIRQi_MASK(index); } while (!reg && --retry); + if (retry == 0) + pair_warn("initialization is not finished\n"); + /* Make the input fifo to ASRC STALL level */ regmap_read(asrc_priv->regmap, REG_ASRCNCR, ®); for (i = 0; i < pair->channels * 4; i++) @@ -492,18 +589,73 @@ static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_RATE, &fsl_asrc_rate_constraints); } +static int fsl_asrc_select_clk(struct fsl_asrc *asrc_priv, + struct fsl_asrc_pair *pair, + int in_rate, + int out_rate) +{ + struct asrc_config *config = pair->config; + int clk_rate; + int clk_index; + int i = 0, j = 0; + int rate[2]; + int select_clk[2]; + bool clk_sel[2]; + + rate[0] = in_rate; + rate[1] = out_rate; + + /*select proper clock for asrc p2p mode*/ + for (j = 0; j < 2; j++) { + for (i = 0; i < CLK_MAP_NUM; i++) { + clk_index = asrc_priv->clk_map[j][i]; + clk_rate = clk_get_rate(asrc_priv->asrck_clk[clk_index]); + if (clk_rate != 0 && (clk_rate / rate[j]) <= 1024 && + (clk_rate % rate[j]) == 0) + break; + } + + if (i == CLK_MAP_NUM) { + select_clk[j] = OUTCLK_ASRCK1_CLK; + clk_sel[j] = false; + } else { + select_clk[j] = i; + clk_sel[j] = true; + } + } + + if (clk_sel[0] != true || clk_sel[1] != true) + select_clk[IN] = INCLK_NONE; + + config->inclk = select_clk[IN]; + config->outclk = select_clk[OUT]; + + /* + * FIXME: workaroud for 176400/192000 with 8 channel input case + * the output sample rate is 48kHz. + * with ideal ratio mode, the asrc seems has performance issue + * that the output sound is not correct. so switch to non-ideal + * ratio mode + */ + if (config->channel_num >= 8 && config->input_sample_rate >= 176400 + && config->inclk == INCLK_NONE) + config->inclk = INCLK_ASRCK1_CLK; + + return 0; +} + static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai); - int width = params_width(params); struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; unsigned int channels = params_channels(params); unsigned int rate = params_rate(params); struct asrc_config config; - int word_width, ret; + snd_pcm_format_t format; + int ret; ret = fsl_asrc_request_pair(channels, pair); if (ret) { @@ -511,39 +663,56 @@ static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, return ret; } + pair->pair_streams |= BIT(substream->stream); pair->config = &config; - if (width == 16) - width = ASRC_WIDTH_16_BIT; - else - width = ASRC_WIDTH_24_BIT; - if (asrc_priv->asrc_width == 16) - word_width = ASRC_WIDTH_16_BIT; + format = SNDRV_PCM_FORMAT_S16_LE; else - word_width = ASRC_WIDTH_24_BIT; + format = SNDRV_PCM_FORMAT_S24_LE; config.pair = pair->index; config.channel_num = channels; - config.inclk = INCLK_NONE; - config.outclk = OUTCLK_ASRCK1_CLK; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - config.input_word_width = width; - config.output_word_width = word_width; + config.input_format = params_format(params); + config.output_format = format; config.input_sample_rate = rate; config.output_sample_rate = asrc_priv->asrc_rate; + + ret = fsl_asrc_select_clk(asrc_priv, pair, + config.input_sample_rate, + config.output_sample_rate); + if (ret) { + dev_err(dai->dev, "fail to select clock\n"); + return ret; + } + + ret = fsl_asrc_config_pair(pair, false, true); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } + } else { - config.input_word_width = word_width; - config.output_word_width = width; + config.input_format = format; + config.output_format = params_format(params); config.input_sample_rate = asrc_priv->asrc_rate; config.output_sample_rate = rate; - } - ret = fsl_asrc_config_pair(pair); - if (ret) { - dev_err(dai->dev, "fail to config asrc pair\n"); - return ret; + ret = fsl_asrc_select_clk(asrc_priv, pair, + config.input_sample_rate, + config.output_sample_rate); + if (ret) { + dev_err(dai->dev, "fail to select clock\n"); + return ret; + } + + ret = fsl_asrc_config_pair(pair, true, false); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } } return 0; @@ -555,8 +724,10 @@ static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; - if (pair) + if (pair && (pair->pair_streams & BIT(substream->stream))) { fsl_asrc_release_pair(pair); + pair->pair_streams &= ~BIT(substream->stream); + } return 0; } @@ -572,6 +743,8 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: fsl_asrc_start_pair(pair); + /* Output enough data to content the DMA burstsize of BE */ + mdelay(1); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: @@ -602,9 +775,13 @@ static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) return 0; } -#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ +#define FSL_ASRC_FORMATS_RX (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE) +#define FSL_ASRC_FORMATS_TX (SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S20_3LE) + SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S24_3LE) static struct snd_soc_dai_driver fsl_asrc_dai = { .probe = fsl_asrc_dai_probe, @@ -615,7 +792,7 @@ static struct snd_soc_dai_driver fsl_asrc_dai = { .rate_min = 5512, .rate_max = 192000, .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS, + .formats = FSL_ASRC_FORMATS_TX, }, .capture = { .stream_name = "ASRC-Capture", @@ -624,7 +801,7 @@ static struct snd_soc_dai_driver fsl_asrc_dai = { .rate_min = 5512, .rate_max = 192000, .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS, + .formats = FSL_ASRC_FORMATS_RX, }, .ops = &fsl_asrc_dai_ops, }; @@ -772,11 +949,15 @@ static const struct regmap_config fsl_asrc_regmap_config = { .cache_type = REGCACHE_FLAT, }; +#include "fsl_asrc_m2m.c" + /** * Initialize ASRC registers with a default configurations */ static int fsl_asrc_init(struct fsl_asrc *asrc_priv) { + unsigned long ipg_rate; + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN); @@ -794,11 +975,12 @@ static int fsl_asrc_init(struct fsl_asrc *asrc_priv) regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1, ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc)); - /* Set the processing clock for 76KHz to 133M */ - regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6); - - /* Set the processing clock for 56KHz to 133M */ - return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947); + ipg_rate = clk_get_rate(asrc_priv->ipg_clk); + /* Set the period of the 76KHz and 56KHz sampling clocks based on + * the ASRC processing clock. + */ + regmap_write(asrc_priv->regmap, REG_ASR76K, ipg_rate / 76000); + return regmap_write(asrc_priv->regmap, REG_ASR56K, ipg_rate / 56000); } /** @@ -877,7 +1059,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) asrc_priv->paddr = res->start; - asrc_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs, + asrc_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, regs, &fsl_asrc_regmap_config); if (IS_ERR(asrc_priv->regmap)) { dev_err(&pdev->dev, "failed to init regmap\n"); @@ -885,8 +1067,10 @@ static int fsl_asrc_probe(struct platform_device *pdev) } irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); return irq; + } ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0, dev_name(&pdev->dev), asrc_priv); @@ -922,20 +1106,46 @@ static int fsl_asrc_probe(struct platform_device *pdev) if (of_device_is_compatible(np, "fsl,imx35-asrc")) { asrc_priv->channel_bits = 3; - clk_map[IN] = input_clk_map_imx35; - clk_map[OUT] = output_clk_map_imx35; - } else { + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx35; + asrc_priv->clk_map[OUT] = output_clk_map_imx35; + asrc_priv->dma_type = DMA_SDMA; + } else if (of_device_is_compatible(np, "fsl,imx53-asrc")) { asrc_priv->channel_bits = 4; - clk_map[IN] = input_clk_map_imx53; - clk_map[OUT] = output_clk_map_imx53; + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx53; + asrc_priv->clk_map[OUT] = output_clk_map_imx53; + asrc_priv->dma_type = DMA_SDMA; + } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc0")) { + asrc_priv->channel_bits = 4; + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx8_0; + asrc_priv->clk_map[OUT] = output_clk_map_imx8_0; + asrc_priv->dma_type = DMA_EDMA; + } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc1")) { + asrc_priv->channel_bits = 4; + strncpy(asrc_priv->name, "mxc_asrc1", + sizeof(asrc_priv->name) - 1); + asrc_priv->clk_map[IN] = input_clk_map_imx8_1; + asrc_priv->clk_map[OUT] = output_clk_map_imx8_1; + asrc_priv->dma_type = DMA_EDMA; } + ret = clk_prepare_enable(asrc_priv->mem_clk); + if (ret) + return ret; + ret = fsl_asrc_init(asrc_priv); if (ret) { dev_err(&pdev->dev, "failed to init asrc %d\n", ret); return ret; } + clk_disable_unprepare(asrc_priv->mem_clk); + asrc_priv->channel_avail = 10; ret = of_property_read_u32(np, "fsl,asrc-rate", @@ -961,6 +1171,8 @@ static int fsl_asrc_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); spin_lock_init(&asrc_priv->lock); + regcache_cache_only(asrc_priv->regmap, true); + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, &fsl_asrc_dai, 1); if (ret) { @@ -968,6 +1180,12 @@ static int fsl_asrc_probe(struct platform_device *pdev) return ret; } + ret = fsl_asrc_m2m_init(asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to init m2m device %d\n", ret); + return ret; + } + return 0; } @@ -976,6 +1194,9 @@ static int fsl_asrc_runtime_resume(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); int i, ret; + u32 asrctr; + u32 reg; + int retry = 50; ret = clk_prepare_enable(asrc_priv->mem_clk); if (ret) @@ -994,6 +1215,34 @@ static int fsl_asrc_runtime_resume(struct device *dev) goto disable_asrck_clk; } + /* Stop all pairs provisionally */ + regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, 0); + + /* Restore all registers */ + regcache_cache_only(asrc_priv->regmap, false); + regcache_mark_dirty(asrc_priv->regmap); + regcache_sync(asrc_priv->regmap); + + regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, + ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK | + ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg); + + /* Restart enabled pairs */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, asrctr); + + /* Wait for status of initialization */ + do { + udelay(5); + regmap_read(asrc_priv->regmap, REG_ASRCFG, ®); + reg = (reg >> ASRCFG_INIRQi_SHIFT(0)) & 0x7; + } while (!(reg == ((asrctr & 0xE) >> 1)) && --retry); + + if (retry == 0) + dev_warn(dev, "initialization is not finished\n"); + return 0; disable_asrck_clk: @@ -1013,6 +1262,11 @@ static int fsl_asrc_runtime_suspend(struct device *dev) struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); int i; + regmap_read(asrc_priv->regmap, REG_ASRCFG, + &asrc_priv->regcache_cfg); + + regcache_cache_only(asrc_priv->regmap, true); + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) clk_disable_unprepare(asrc_priv->asrck_clk[i]); if (!IS_ERR(asrc_priv->spba_clk)) @@ -1028,39 +1282,25 @@ static int fsl_asrc_runtime_suspend(struct device *dev) static int fsl_asrc_suspend(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int ret; - regmap_read(asrc_priv->regmap, REG_ASRCFG, - &asrc_priv->regcache_cfg); + fsl_asrc_m2m_suspend(asrc_priv); - regcache_cache_only(asrc_priv->regmap, true); - regcache_mark_dirty(asrc_priv->regmap); + ret = pm_runtime_force_suspend(dev); - return 0; + return ret; } static int fsl_asrc_resume(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); - u32 asrctr; + int ret; - /* Stop all pairs provisionally */ - regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); - regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_ASRCEi_ALL_MASK, 0); + ret = pm_runtime_force_resume(dev); - /* Restore all registers */ - regcache_cache_only(asrc_priv->regmap, false); - regcache_sync(asrc_priv->regmap); + fsl_asrc_m2m_resume(asrc_priv); - regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, - ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK | - ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg); - - /* Restart enabled pairs */ - regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, - ASRCTR_ASRCEi_ALL_MASK, asrctr); - - return 0; + return ret; } #endif /* CONFIG_PM_SLEEP */ @@ -1072,12 +1312,15 @@ static const struct dev_pm_ops fsl_asrc_pm = { static const struct of_device_id fsl_asrc_ids[] = { { .compatible = "fsl,imx35-asrc", }, { .compatible = "fsl,imx53-asrc", }, + { .compatible = "fsl,imx8qm-asrc0", }, + { .compatible = "fsl,imx8qm-asrc1", }, {} }; MODULE_DEVICE_TABLE(of, fsl_asrc_ids); static struct platform_driver fsl_asrc_driver = { .probe = fsl_asrc_probe, + .remove = fsl_asrc_m2m_remove, .driver = { .name = "fsl-asrc", .of_match_table = fsl_asrc_ids, diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h index c60075112570..c3c17769aa7e 100644 --- a/sound/soc/fsl/fsl_asrc.h +++ b/sound/soc/fsl/fsl_asrc.h @@ -2,7 +2,7 @@ /* * fsl_asrc.h - Freescale ASRC ALSA SoC header file * - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. * * Author: Nicolin Chen <nicoleotsuka@gmail.com> */ @@ -10,6 +10,12 @@ #ifndef _FSL_ASRC_H #define _FSL_ASRC_H +#include <sound/asound.h> +#include <uapi/linux/mxc_asrc.h> +#include <linux/miscdevice.h> + +#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) + #define IN 0 #define OUT 1 @@ -20,7 +26,8 @@ #define ASRC_FIFO_THRESHOLD_MAX 63 #define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4) #define ASRC_MAX_BUFFER_SIZE (1024 * 48) -#define ASRC_OUTPUT_LAST_SAMPLE 8 +#define ASRC_OUTPUT_LAST_SAMPLE_MAX 32 +#define ASRC_OUTPUT_LAST_SAMPLE 4 #define IDEAL_RATIO_RATE 1000000 @@ -283,106 +290,15 @@ #define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT) #define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT) - -enum asrc_pair_index { - ASRC_INVALID_PAIR = -1, - ASRC_PAIR_A = 0, - ASRC_PAIR_B = 1, - ASRC_PAIR_C = 2, -}; - -#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) - -enum asrc_inclk { - INCLK_NONE = 0x03, - INCLK_ESAI_RX = 0x00, - INCLK_SSI1_RX = 0x01, - INCLK_SSI2_RX = 0x02, - INCLK_SSI3_RX = 0x07, - INCLK_SPDIF_RX = 0x04, - INCLK_MLB_CLK = 0x05, - INCLK_PAD = 0x06, - INCLK_ESAI_TX = 0x08, - INCLK_SSI1_TX = 0x09, - INCLK_SSI2_TX = 0x0a, - INCLK_SSI3_TX = 0x0b, - INCLK_SPDIF_TX = 0x0c, - INCLK_ASRCK1_CLK = 0x0f, -}; - -enum asrc_outclk { - OUTCLK_NONE = 0x03, - OUTCLK_ESAI_TX = 0x00, - OUTCLK_SSI1_TX = 0x01, - OUTCLK_SSI2_TX = 0x02, - OUTCLK_SSI3_TX = 0x07, - OUTCLK_SPDIF_TX = 0x04, - OUTCLK_MLB_CLK = 0x05, - OUTCLK_PAD = 0x06, - OUTCLK_ESAI_RX = 0x08, - OUTCLK_SSI1_RX = 0x09, - OUTCLK_SSI2_RX = 0x0a, - OUTCLK_SSI3_RX = 0x0b, - OUTCLK_SPDIF_RX = 0x0c, - OUTCLK_ASRCK1_CLK = 0x0f, -}; - #define ASRC_CLK_MAX_NUM 16 enum asrc_word_width { ASRC_WIDTH_24_BIT = 0, ASRC_WIDTH_16_BIT = 1, - ASRC_WIDTH_8_BIT = 2, -}; - -struct asrc_config { - enum asrc_pair_index pair; - unsigned int channel_num; - unsigned int buffer_num; - unsigned int dma_buffer_size; - unsigned int input_sample_rate; - unsigned int output_sample_rate; - enum asrc_word_width input_word_width; - enum asrc_word_width output_word_width; - enum asrc_inclk inclk; - enum asrc_outclk outclk; -}; - -struct asrc_req { - unsigned int chn_num; - enum asrc_pair_index index; -}; - -struct asrc_querybuf { - unsigned int buffer_index; - unsigned int input_length; - unsigned int output_length; - unsigned long input_offset; - unsigned long output_offset; -}; - -struct asrc_convert_buffer { - void *input_buffer_vaddr; - void *output_buffer_vaddr; - unsigned int input_buffer_length; - unsigned int output_buffer_length; -}; - -struct asrc_status_flags { - enum asrc_pair_index index; - unsigned int overload_error; -}; - -enum asrc_error_status { - ASRC_TASK_Q_OVERLOAD = 0x01, - ASRC_OUTPUT_TASK_OVERLOAD = 0x02, - ASRC_INPUT_TASK_OVERLOAD = 0x04, - ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, - ASRC_INPUT_BUFFER_UNDERRUN = 0x10, + ASRC_WIDTH_8_BIT = 2, }; struct dma_block { - dma_addr_t dma_paddr; void *dma_vaddr; unsigned int length; }; @@ -413,6 +329,7 @@ struct fsl_asrc_pair { struct dma_chan *dma_chan[2]; struct imx_dma_data dma_data; unsigned int pos; + unsigned int pair_streams; void *private; }; @@ -433,6 +350,7 @@ struct fsl_asrc_pair { * @pair: pair pointers * @channel_bits: width of ASRCNCR register for each pair * @channel_avail: non-occupied channel numbers + * @pair_streams:indicat which substream is running * @asrc_rate: default sample rate for ASoC Back-Ends * @asrc_width: default sample width for ASoC Back-Ends * @regcache_cfg: store register value of REG_ASRCFG @@ -447,19 +365,29 @@ struct fsl_asrc { struct clk *ipg_clk; struct clk *spba_clk; struct clk *asrck_clk[ASRC_CLK_MAX_NUM]; + unsigned char *clk_map[2]; spinlock_t lock; struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM]; + struct miscdevice asrc_miscdev; unsigned int channel_bits; unsigned int channel_avail; int asrc_rate; int asrc_width; + int dma_type; /* 0 is sdma, 1 is edma */ u32 regcache_cfg; + char name[20]; }; +#define DMA_SDMA 0 +#define DMA_EDMA 1 + #define DRV_NAME "fsl-asrc-dai" extern struct snd_soc_component_driver fsl_asrc_component; struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir); +int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair); +void fsl_asrc_release_pair(struct fsl_asrc_pair *pair); + #endif /* _FSL_ASRC_H */ diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 5aee6b8366d2..bd435aa6591b 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -16,16 +16,14 @@ #define FSL_ASRC_DMABUF_SIZE (256 * 1024) -static const struct snd_pcm_hardware snd_imx_hardware = { +static struct snd_pcm_hardware snd_imx_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME, + SNDRV_PCM_INFO_MMAP_VALID, .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, .period_bytes_min = 128, - .period_bytes_max = 65535, /* Limited by SDMA engine */ + .period_bytes_max = 65532, /* Limited by SDMA engine */ .periods_min = 2, .periods_max = 255, .fifo_size = 0, @@ -148,6 +146,7 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, struct device *dev_be; u8 dir = tx ? OUT : IN; dma_cap_mask_t mask; + enum sdma_peripheral_type be_peripheral_type; int ret; /* Fetch the Back-End dma_data from DPCM */ @@ -201,19 +200,43 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, /* Get DMA request of Back-End */ tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); - tmp_data = tmp_chan->private; - pair->dma_data.dma_request = tmp_data->dma_request; - dma_release_channel(tmp_chan); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + pair->dma_data.dma_request = tmp_data->dma_request; + be_peripheral_type = tmp_data->peripheral_type; + if (tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + pair->dma_data.dst_dualfifo = true; + if (!tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + pair->dma_data.src_dualfifo = true; + } + dma_release_channel(tmp_chan); + } /* Get DMA request of Front-End */ tmp_chan = fsl_asrc_get_dma_channel(pair, dir); - tmp_data = tmp_chan->private; - pair->dma_data.dma_request2 = tmp_data->dma_request; - pair->dma_data.peripheral_type = tmp_data->peripheral_type; - pair->dma_data.priority = tmp_data->priority; - dma_release_channel(tmp_chan); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + pair->dma_data.dma_request2 = tmp_data->dma_request; + pair->dma_data.peripheral_type = + tmp_data->peripheral_type; + pair->dma_data.priority = tmp_data->priority; + } + dma_release_channel(tmp_chan); + } + + /* For sdma DEV_TO_DEV, there is two dma request + * But for emda DEV_TO_DEV, there is only one dma request, which is + * from the BE. + */ + if (pair->dma_data.dma_request2 != pair->dma_data.dma_request) + pair->dma_chan[dir] = + dma_request_channel(mask, filter, &pair->dma_data); + else + pair->dma_chan[dir] = + dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); - pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data); if (!pair->dma_chan[dir]) { dev_err(dev, "failed to request DMA channel for Back-End\n"); return -EINVAL; @@ -224,6 +247,8 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, else buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + memset(&config_be, 0, sizeof(config_be)); + config_be.direction = DMA_DEV_TO_DEV; config_be.src_addr_width = buswidth; config_be.src_maxburst = dma_params_be->maxburst; @@ -277,6 +302,16 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) struct device *dev = component->dev; struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); struct fsl_asrc_pair *pair; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 dir = tx ? OUT : IN; + struct dma_slave_caps dma_caps; + struct dma_chan *tmp_chan; + struct snd_dmaengine_dai_dma_data *dma_data; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); if (!pair) @@ -286,8 +321,78 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) runtime->private_data = pair; - snd_pcm_hw_constraint_integer(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIODS); + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "failed to set pcm hw params periods\n"); + return ret; + } + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + fsl_asrc_request_pair(1, pair); + + tmp_chan = fsl_asrc_get_dma_channel(pair, dir); + if (!tmp_chan) { + dev_err(dev, "can't get dma channel\n"); + return -EINVAL; + } + + ret = dma_get_slave_caps(tmp_chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + snd_imx_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + snd_imx_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + snd_imx_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + if (tmp_chan) + dma_release_channel(tmp_chan); + fsl_asrc_release_pair(pair); + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); return 0; diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c new file mode 100644 index 000000000000..06d653a1757a --- /dev/null +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -0,0 +1,1050 @@ +/* + * Freescale ASRC Memory to Memory (M2M) driver + * + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#define FSL_ASRC_INPUTFIFO_WML 0x4 +#define FSL_ASRC_OUTPUTFIFO_WML 0x2 + +#define DIR_STR(dir) dir == IN ? "in" : "out" + +struct fsl_asrc_m2m { + struct fsl_asrc_pair *pair; + struct completion complete[2]; + struct dma_block dma_block[2]; + unsigned int pair_hold; + unsigned int asrc_active; + unsigned int sg_nodes[2]; + struct scatterlist sg[2][4]; + + snd_pcm_format_t word_format[2]; + unsigned int rate[2]; + unsigned int last_period_size; + u32 watermark[2]; + spinlock_t lock; +}; + +static void fsl_asrc_get_status(struct fsl_asrc_pair *pair, + struct asrc_status_flags *flags) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned long lock_flags; + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + flags->overload_error = pair->error; + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); +} + +#define ASRC_xPUT_DMA_CALLBACK(dir) \ + ((dir == IN) ? fsl_asrc_input_dma_callback : fsl_asrc_output_dma_callback) + +static void fsl_asrc_input_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private; + + complete(&m2m->complete[IN]); +} + +static void fsl_asrc_output_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private; + + complete(&m2m->complete[OUT]); +} + +static unsigned int fsl_asrc_get_output_FIFO_size(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + u32 val; + + regmap_read(asrc_priv->regmap, REG_ASRFST(index), &val); + + val &= ASRFSTi_OUTPUT_FIFO_MASK; + + return val >> ASRFSTi_OUTPUT_FIFO_SHIFT; +} + +static void fsl_asrc_read_last_FIFO(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct dma_block *output = &m2m->dma_block[OUT]; + u32 i, reg, size, t_size = 0, width; + u32 *reg32 = NULL; + u16 *reg16 = NULL; + u8 *reg24 = NULL; + + width = snd_pcm_format_physical_width(m2m->word_format[OUT]); + + if (width == 32) + reg32 = output->dma_vaddr + output->length; + else if (width == 16) + reg16 = output->dma_vaddr + output->length; + else + reg24 = output->dma_vaddr + output->length; + +retry: + size = fsl_asrc_get_output_FIFO_size(pair); + + for (i = 0; i < size * pair->channels; i++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + if (reg32) { + *(reg32) = reg; + reg32++; + } else if (reg16) { + *(reg16) = (u16)reg; + reg16++; + } else { + *reg24++ = (u8)reg; + *reg24++ = (u8)(reg >> 8); + *reg24++ = (u8)(reg >> 16); + } + } + t_size += size; + + if (size) + goto retry; + + if (t_size > m2m->last_period_size) + t_size = m2m->last_period_size; + + if (reg32) + output->length += t_size * pair->channels * 4; + else if (reg16) + output->length += t_size * pair->channels * 2; + else + output->length += t_size * pair->channels * 3; +} + +static int fsl_allocate_dma_buf(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct dma_block *input = &m2m->dma_block[IN]; + struct dma_block *output = &m2m->dma_block[OUT]; + enum asrc_pair_index index = pair->index; + + input->dma_vaddr = kzalloc(input->length, GFP_KERNEL); + if (!input->dma_vaddr) { + pair_err("failed to allocate input DMA buffer\n"); + return -ENOMEM; + } + + output->dma_vaddr = kzalloc(output->length, GFP_KERNEL); + if (!output->dma_vaddr) { + pair_err("failed to allocate output DMA buffer\n"); + goto exit; + } + + return 0; + +exit: + kfree(input->dma_vaddr); + + return -ENOMEM; +} + +static int fsl_asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan, + u32 dma_addr, void *buf_addr, u32 buf_len, + bool dir, snd_pcm_format_t word_format) +{ + struct dma_async_tx_descriptor *desc = pair->desc[dir]; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + unsigned int sg_nent = m2m->sg_nodes[dir]; + enum asrc_pair_index index = pair->index; + struct scatterlist *sg = m2m->sg[dir]; + struct dma_slave_config slave_config; + enum dma_slave_buswidth buswidth; + int ret, i; + + switch (snd_pcm_format_physical_width(word_format)) { + case 8: + buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; + break; + case 16: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 24: + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; + break; + case 32: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + pair_err("invalid word width\n"); + return -EINVAL; + } + + if (dir == IN) { + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.dst_addr = dma_addr; + slave_config.dst_addr_width = buswidth; + if (asrc_priv->dma_type == DMA_SDMA) + slave_config.dst_maxburst = + m2m->watermark[IN] * pair->channels; + else + slave_config.dst_maxburst = 1; + } else { + slave_config.direction = DMA_DEV_TO_MEM; + slave_config.src_addr = dma_addr; + slave_config.src_addr_width = buswidth; + if (asrc_priv->dma_type == DMA_SDMA) + slave_config.src_maxburst = + m2m->watermark[OUT] * pair->channels; + else + slave_config.src_maxburst = 1; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) { + pair_err("failed to config dmaengine for %sput task: %d\n", + DIR_STR(dir), ret); + return -EINVAL; + } + + sg_init_table(sg, sg_nent); + switch (sg_nent) { + case 1: + sg_init_one(sg, buf_addr, buf_len); + break; + case 2: + case 3: + case 4: + for (i = 0; i < (sg_nent - 1); i++) + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + ASRC_MAX_BUFFER_SIZE); + + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + buf_len - ASRC_MAX_BUFFER_SIZE * i); + break; + default: + pair_err("invalid input DMA nodes number: %d\n", sg_nent); + return -EINVAL; + } + + ret = dma_map_sg(&asrc_priv->pdev->dev, sg, sg_nent, slave_config.direction); + if (ret != sg_nent) { + pair_err("failed to map DMA sg for %sput task\n", DIR_STR(dir)); + return -EINVAL; + } + + desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, + slave_config.direction, DMA_PREP_INTERRUPT); + if (!desc) { + pair_err("failed to prepare dmaengine for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + pair->desc[dir] = desc; + pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir); + + desc->callback = ASRC_xPUT_DMA_CALLBACK(dir); + desc->callback_param = pair; + + return 0; +} + +static int fsl_asrc_prepare_io_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf, bool dir) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned int *dma_len = &m2m->dma_block[dir].length; + void *dma_vaddr = m2m->dma_block[dir].dma_vaddr; + struct dma_chan *dma_chan = pair->dma_chan[dir]; + unsigned int buf_len, wm = m2m->watermark[dir]; + unsigned int *sg_nodes = &m2m->sg_nodes[dir]; + unsigned int last_period_size = m2m->last_period_size; + enum asrc_pair_index index = pair->index; + u32 word_size, fifo_addr; + void __user *buf_vaddr; + + /* Clean the DMA buffer */ + memset(dma_vaddr, 0, ASRC_DMA_BUFFER_SIZE); + + if (dir == IN) { + buf_vaddr = (void __user *)pbuf->input_buffer_vaddr; + buf_len = pbuf->input_buffer_length; + } else { + buf_vaddr = (void __user *)pbuf->output_buffer_vaddr; + buf_len = pbuf->output_buffer_length; + } + + word_size = snd_pcm_format_physical_width(m2m->word_format[dir]) / 8; + + if (buf_len < word_size * pair->channels * wm || + buf_len > ASRC_DMA_BUFFER_SIZE || + (dir == OUT && buf_len < word_size * pair->channels * last_period_size)) { + pair_err("%sput buffer size is error: [%d]\n", + DIR_STR(dir), buf_len); + return -EINVAL; + } + + /* Copy origin data into input buffer */ + if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) + return -EFAULT; + + *dma_len = buf_len; + if (dir == OUT) { + *dma_len -= last_period_size * word_size * pair->channels; + *dma_len = *dma_len / (word_size * pair->channels) * + (word_size * pair->channels); + if (asrc_priv->dma_type == DMA_EDMA) + *dma_len = *dma_len / (word_size * pair->channels * m2m->watermark[OUT]) + * (word_size * pair->channels * m2m->watermark[OUT]); + } + + *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE; + if (*dma_len % ASRC_MAX_BUFFER_SIZE) + *sg_nodes += 1; + + fifo_addr = asrc_priv->paddr + REG_ASRDx(dir, index); + + return fsl_asrc_dmaconfig(pair, dma_chan, fifo_addr, dma_vaddr, + *dma_len, dir, m2m->word_format[dir]); +} + +static int fsl_asrc_prepare_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + int ret; + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, IN); + if (ret) { + pair_err("failed to prepare input buffer: %d\n", ret); + return ret; + } + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, OUT); + if (ret) { + pair_err("failed to prepare output buffer: %d\n", ret); + return ret; + } + + return 0; +} + +int fsl_asrc_process_buffer_pre(struct completion *complete, + enum asrc_pair_index index, bool dir) +{ + if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) { + pr_err("%sput DMA task timeout\n", DIR_STR(dir)); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("%sput task forcibly aborted\n", DIR_STR(dir)); + return -ERESTARTSYS; + } + + return 0; +} + +#define mxc_asrc_dma_umap(dev, m2m) \ + do { \ + dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \ + DMA_MEM_TO_DEV); \ + dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \ + DMA_DEV_TO_MEM); \ + } while (0) + +int fsl_asrc_process_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + unsigned long lock_flags; + int ret; + + /* Check input task first */ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[IN], index, IN); + if (ret) { + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + return ret; + } + + /* ...then output task*/ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, OUT); + if (ret) { + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + return ret; + } + + mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m); + + /* Fetch the remaining data */ + spin_lock_irqsave(&m2m->lock, lock_flags); + if (!m2m->pair_hold) { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + return -EFAULT; + } + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + fsl_asrc_read_last_FIFO(pair); + + /* Update final lengths after getting last FIFO */ + pbuf->input_buffer_length = m2m->dma_block[IN].length; + pbuf->output_buffer_length = m2m->dma_block[OUT].length; + + if (copy_to_user((void __user *)pbuf->output_buffer_vaddr, + m2m->dma_block[OUT].dma_vaddr, + m2m->dma_block[OUT].length)) + return -EFAULT; + + return 0; +} + +#ifdef ASRC_POLLING_WITHOUT_DMA +/* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */ +static void fsl_asrc_polling_debug(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + u32 *in24 = m2m->dma_block[IN].dma_vaddr; + u32 dma_len = m2m->dma_block[IN].length / (pair->channels * 4); + u32 *reg24 = m2m->dma_block[OUT].dma_vaddr; + u32 size, i, j, t_size, reg; + + t_size = 0; + + for (i = 0; i < dma_len; ) { + for (j = 0; j < 2; j++) { + regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24); + in24++; + regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24); + in24++; + i++; + } + udelay(50); + udelay(50 * m2m->rate[OUT] / m2m->rate[IN]); + + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + } + + mdelay(1); + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + + m2m->dma_block[OUT].length = t_size * pair->channels * 4; + + complete(&m2m->complete[OUT]); + complete(&m2m->complete[IN]); +} +#else +static void fsl_asrc_submit_dma(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + u32 size = fsl_asrc_get_output_FIFO_size(pair); + int i; + + /* Read all data in OUTPUT FIFO */ + while (size) { + u32 val; + for (i = 0; i < size * pair->channels; i++) + regmap_read(asrc_priv->regmap, REG_ASRDO(index), &val); + /* Fetch the data every 100us */ + udelay(100); + + size = fsl_asrc_get_output_FIFO_size(pair); + } + + /* Submit DMA request */ + dmaengine_submit(pair->desc[IN]); + dma_async_issue_pending(pair->desc[IN]->chan); + + dmaengine_submit(pair->desc[OUT]); + dma_async_issue_pending(pair->desc[OUT]->chan); + + /* + * Clear DMA request during the stall state of ASRC: + * During STALL state, the remaining in input fifo would never be + * smaller than the input threshold while the output fifo would not + * be bigger than output one. Thus the DMA request would be cleared. + */ + fsl_asrc_set_watermarks(pair, ASRC_FIFO_THRESHOLD_MIN, + ASRC_FIFO_THRESHOLD_MAX); + + /* Update the real input threshold to raise DMA request */ + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); +} +#endif /* ASRC_POLLING_WITHOUT_DMA */ + +static long fsl_asrc_ioctl_req_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + struct device *dev = &asrc_priv->pdev->dev; + struct asrc_req req; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&req, user, sizeof(req)); + if (ret) { + dev_err(dev, "failed to get req from user space: %ld\n", ret); + return ret; + } + + ret = fsl_asrc_request_pair(req.chn_num, pair); + if (ret) { + dev_err(dev, "failed to request pair: %ld\n", ret); + return ret; + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 1; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + pair->channels = req.chn_num; + + req.index = pair->index; + req.supported_in_format = FSL_ASRC_FORMATS_TX; + req.supported_out_format = FSL_ASRC_FORMATS_RX; + if (asrc_priv->dma_type == DMA_EDMA) { + req.supported_in_format &= ~SNDRV_PCM_FMTBIT_S24_3LE; + req.supported_out_format &= ~SNDRV_PCM_FMTBIT_S24_3LE; + } + + ret = copy_to_user(user, &req, sizeof(req)); + if (ret) { + dev_err(dev, "failed to send req to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_config_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + struct device *dev = &asrc_priv->pdev->dev; + struct asrc_config config; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&config, user, sizeof(config)); + if (ret) { + dev_err(dev, "failed to get config from user space: %ld\n", ret); + return ret; + } + + index = config.pair; + + pair->config = &config; + ret = fsl_asrc_config_pair(pair, false, false); + if (ret) { + pair_err("failed to config pair: %ld\n", ret); + return ret; + } + + m2m->watermark[IN] = FSL_ASRC_INPUTFIFO_WML; + m2m->watermark[OUT] = FSL_ASRC_OUTPUTFIFO_WML; + + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); + + m2m->dma_block[IN].length = ASRC_DMA_BUFFER_SIZE; + m2m->dma_block[OUT].length = ASRC_DMA_BUFFER_SIZE; + + m2m->word_format[IN] = config.input_format; + m2m->word_format[OUT] = config.output_format; + + m2m->rate[IN] = config.input_sample_rate; + m2m->rate[OUT] = config.output_sample_rate; + + m2m->last_period_size = ASRC_OUTPUT_LAST_SAMPLE; + + ret = fsl_allocate_dma_buf(pair); + if (ret) { + pair_err("failed to allocate DMA buffer: %ld\n", ret); + return ret; + } + + /* Request DMA channel for both input and output */ + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + ret = copy_to_user(user, &config, sizeof(config)); + if (ret) { + pair_err("failed to send config to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_release_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + /* index might be not valid due to some application failure. */ + if (index < 0) + return -EINVAL; + + m2m->asrc_active = 0; + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + fsl_asrc_release_pair(pair); + + return 0; +} + +static long fsl_asrc_calc_last_period_size(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned int out_length; + unsigned int in_width, out_width; + unsigned int channels = pair->channels; + unsigned int in_samples, out_samples; + unsigned int last_period_size; + unsigned int remain; + + in_width = snd_pcm_format_physical_width(m2m->word_format[IN]) / 8; + out_width = snd_pcm_format_physical_width(m2m->word_format[OUT]) / 8; + + in_samples = pbuf->input_buffer_length / (in_width * channels); + + out_samples = (m2m->rate[OUT] * in_samples / m2m->rate[IN]); + + out_length = out_samples * out_width * channels; + + last_period_size = pbuf->output_buffer_length / (out_width * channels) + - out_samples; + + m2m->last_period_size = last_period_size + 1 + ASRC_OUTPUT_LAST_SAMPLE; + + if (asrc_priv->dma_type == DMA_EDMA) { + remain = pbuf->output_buffer_length % (out_width * channels * m2m->watermark[OUT]); + if (remain) + m2m->last_period_size += remain / (out_width * channels); + } + + return 0; +} + +static long fsl_asrc_ioctl_convert(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct asrc_convert_buffer buf; + long ret; + + ret = copy_from_user(&buf, user, sizeof(buf)); + if (ret) { + pair_err("failed to get buf from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_calc_last_period_size(pair, &buf); + + ret = fsl_asrc_prepare_buffer(pair, &buf); + if (ret) { + pair_err("failed to prepare buffer: %ld\n", ret); + return ret; + } + + reinit_completion(&m2m->complete[IN]); + reinit_completion(&m2m->complete[OUT]); + +#ifdef ASRC_POLLING_WITHOUT_DMA + fsl_asrc_polling_debug(pair); +#else + fsl_asrc_submit_dma(pair); +#endif + + ret = fsl_asrc_process_buffer(pair, &buf); + if (ret) { + if (ret != -ERESTARTSYS) + pair_err("failed to process buffer: %ld\n", ret); + return ret; + } + + ret = copy_to_user(user, &buf, sizeof(buf)); + if (ret) { + pair_err("failed to send buf to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_start_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + m2m->asrc_active = 1; + fsl_asrc_start_pair(pair); + + return 0; +} + +static long fsl_asrc_ioctl_stop_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + m2m->asrc_active = 0; + + return 0; +} + +static long fsl_asrc_ioctl_status(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct asrc_status_flags flags; + long ret; + + ret = copy_from_user(&flags, user, sizeof(flags)); + if (ret) { + pair_err("failed to get flags from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_get_status(pair, &flags); + + ret = copy_to_user(user, &flags, sizeof(flags)); + if (ret) { + pair_err("failed to send flags to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_flush(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + /* Release DMA and request again */ + dma_release_channel(pair->dma_chan[IN]); + dma_release_channel(pair->dma_chan[OUT]); + + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static long fsl_asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + void __user *user = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case ASRC_REQ_PAIR: + ret = fsl_asrc_ioctl_req_pair(pair, user); + break; + case ASRC_CONFIG_PAIR: + ret = fsl_asrc_ioctl_config_pair(pair, user); + break; + case ASRC_RELEASE_PAIR: + ret = fsl_asrc_ioctl_release_pair(pair, user); + break; + case ASRC_CONVERT: + ret = fsl_asrc_ioctl_convert(pair, user); + break; + case ASRC_START_CONV: + ret = fsl_asrc_ioctl_start_conv(pair, user); + break; + case ASRC_STOP_CONV: + ret = fsl_asrc_ioctl_stop_conv(pair, user); + break; + case ASRC_STATUS: + ret = fsl_asrc_ioctl_status(pair, user); + break; + case ASRC_FLUSH: + ret = fsl_asrc_ioctl_flush(pair, user); + break; + default: + dev_err(&asrc_priv->pdev->dev, "invalid ioctl cmd!\n"); + break; + } + + return ret; +} + +static int fsl_asrc_open(struct inode *inode, struct file *file) +{ + struct miscdevice *asrc_miscdev = file->private_data; + struct fsl_asrc *asrc_priv = dev_get_drvdata(asrc_miscdev->parent); + struct device *dev = &asrc_priv->pdev->dev; + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + int ret; + + ret = signal_pending(current); + if (ret) { + dev_err(dev, "current process has a signal pending\n"); + return ret; + } + + pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); + if (!pair) { + dev_err(dev, "failed to allocate pair\n"); + return -ENOMEM; + } + + m2m = kzalloc(sizeof(struct fsl_asrc_m2m), GFP_KERNEL); + if (!m2m) { + dev_err(dev, "failed to allocate m2m resource\n"); + ret = -ENOMEM; + goto out; + } + + pair->private = m2m; + pair->asrc_priv = asrc_priv; + + spin_lock_init(&m2m->lock); + init_completion(&m2m->complete[IN]); + init_completion(&m2m->complete[OUT]); + + file->private_data = pair; + + pm_runtime_get_sync(dev); + + return 0; +out: + kfree(pair); + + return ret; +} + +static int fsl_asrc_close(struct inode *inode, struct file *file) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct device *dev = &asrc_priv->pdev->dev; + unsigned long lock_flags; + + if (m2m->asrc_active) { + m2m->asrc_active = 0; + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + fsl_asrc_input_dma_callback((void *)pair); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (m2m->pair_hold) { + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_asrc_release_pair(pair); + } else + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + kfree(m2m); + kfree(pair); + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + file->private_data = NULL; + + pm_runtime_put_sync(dev); + + return 0; +} + +static const struct file_operations asrc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_asrc_ioctl, + .open = fsl_asrc_open, + .release = fsl_asrc_close, +}; + +static int fsl_asrc_m2m_init(struct fsl_asrc *asrc_priv) +{ + struct device *dev = &asrc_priv->pdev->dev; + int ret; + + asrc_priv->asrc_miscdev.fops = &asrc_fops; + asrc_priv->asrc_miscdev.parent = dev; + asrc_priv->asrc_miscdev.name = asrc_priv->name; + asrc_priv->asrc_miscdev.minor = MISC_DYNAMIC_MINOR; + ret = misc_register(&asrc_priv->asrc_miscdev); + if (ret) { + dev_err(dev, "failed to register char device %d\n", ret); + return ret; + } + + return 0; +} + +static int fsl_asrc_m2m_remove(struct platform_device *pdev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(&pdev->dev); + + misc_deregister(&asrc_priv->asrc_miscdev); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static void fsl_asrc_m2m_suspend(struct fsl_asrc *asrc_priv) +{ + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + unsigned long lock_flags; + int i; + + for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + pair = asrc_priv->pair[i]; + if (!pair || !pair->private) { + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + continue; + } + m2m = pair->private; + + if (!completion_done(&m2m->complete[IN])) { + if (pair->dma_chan[IN]) + dmaengine_terminate_all(pair->dma_chan[IN]); + fsl_asrc_input_dma_callback((void *)pair); + } + if (!completion_done(&m2m->complete[OUT])) { + if (pair->dma_chan[OUT]) + dmaengine_terminate_all(pair->dma_chan[OUT]); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + } +} + +static void fsl_asrc_m2m_resume(struct fsl_asrc *asrc_priv) +{ + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + unsigned long lock_flags; + enum asrc_pair_index index; + int i, j; + + for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + pair = asrc_priv->pair[i]; + if (!pair || !pair->private) { + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + continue; + } + m2m = pair->private; + index = pair->index; + + for (j = 0; j < pair->channels * 4; j++) + regmap_write(asrc_priv->regmap, REG_ASRDI(index), 0); + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + } +} +#endif diff --git a/sound/soc/fsl/fsl_aud2htx.c b/sound/soc/fsl/fsl_aud2htx.c new file mode 100644 index 000000000000..b542a1ae486b --- /dev/null +++ b/sound/soc/fsl/fsl_aud2htx.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Freescale ALSA SoC Digital Audio Interface (SAI) driver. +// +// Copyright 2012-2016 Freescale Semiconductor, Inc. + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/pm_qos.h> +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <linux/dma-mapping.h> +#include "fsl_aud2htx.h" +#include "imx-pcm.h" + +static int fsl_aud2htx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_aud2htx *aud2htx = snd_soc_dai_get_drvdata(cpu_dai); + + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_DT_MASK, 0); + + regmap_update_bits(aud2htx->regmap, AUD2HTX_IRQ_MASK, + AUD2HTX_WM_HIGH_IRQ_MASK | + AUD2HTX_WM_LOW_IRQ_MASK | + AUD2HTX_OVF_MASK, + AUD2HTX_WM_HIGH_IRQ_MASK | + AUD2HTX_WM_LOW_IRQ_MASK | + AUD2HTX_OVF_MASK); + + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_WL_MASK, + 0x10 << AUD2HTX_CTRE_WL_SHIFT); + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_WH_MASK, + 0x10 << AUD2HTX_CTRE_WH_SHIFT); + return 0; +} + +static int fsl_aud2htx_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_aud2htx *aud2htx = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL, + AUD2HTX_CTRL_EN, AUD2HTX_CTRL_EN); + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_DE, AUD2HTX_CTRE_DE); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_DE, 0); + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL, + AUD2HTX_CTRL_EN, 0); + break; + default: + return -EINVAL; + } + return 0; +} + +static int fsl_aud2htx_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int fsl_aud2htx_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + return 0; +} + +static int fsl_aud2htx_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + return 0; +} + +static const struct snd_soc_dai_ops fsl_aud2htx_dai_ops = { + .hw_params = fsl_aud2htx_hw_params, + .trigger = fsl_aud2htx_trigger, + .set_sysclk = fsl_aud2htx_set_dai_sysclk, + .set_fmt = fsl_aud2htx_set_dai_fmt, + .set_tdm_slot = fsl_aud2htx_set_dai_tdm_slot, +}; + +static int fsl_aud2htx_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_aud2htx *aud2htx = dev_get_drvdata(cpu_dai->dev); + + snd_soc_dai_init_dma_data(cpu_dai, &aud2htx->dma_params_tx, + &aud2htx->dma_params_rx); + + return 0; +} + +static struct snd_soc_dai_driver fsl_aud2htx_dai = { + .probe = fsl_aud2htx_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = FSL_AUD2HTX_FORMATS, + }, + .ops = &fsl_aud2htx_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_aud2htx_component = { + .name = "fsl-aud2htx", +}; + +static const struct reg_default fsl_aud2htx_reg_defaults[] = { + {AUD2HTX_CTRL, 0x00000000}, + {AUD2HTX_CTRL_EXT, 0x00000000}, + {AUD2HTX_WR, 0x00000000}, + {AUD2HTX_STATUS, 0x00000000}, + {AUD2HTX_IRQ_NOMASK, 0x00000000}, + {AUD2HTX_IRQ_MASKED, 0x00000000}, + {AUD2HTX_IRQ_MASK, 0x00000000}, +}; + +static bool fsl_aud2htx_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AUD2HTX_CTRL: + case AUD2HTX_CTRL_EXT: + case AUD2HTX_STATUS: + case AUD2HTX_IRQ_NOMASK: + case AUD2HTX_IRQ_MASKED: + case AUD2HTX_IRQ_MASK: + return true; + default: + return false; + } +} + +static bool fsl_aud2htx_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AUD2HTX_CTRL: + case AUD2HTX_CTRL_EXT: + case AUD2HTX_WR: + case AUD2HTX_IRQ_NOMASK: + case AUD2HTX_IRQ_MASKED: + case AUD2HTX_IRQ_MASK: + return true; + default: + return false; + } +} + +static bool fsl_aud2htx_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AUD2HTX_STATUS: + case AUD2HTX_IRQ_NOMASK: + case AUD2HTX_IRQ_MASKED: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_aud2htx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = AUD2HTX_IRQ_MASK, + .reg_defaults = fsl_aud2htx_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_aud2htx_reg_defaults), + .readable_reg = fsl_aud2htx_readable_reg, + .volatile_reg = fsl_aud2htx_volatile_reg, + .writeable_reg = fsl_aud2htx_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct of_device_id fsl_aud2htx_dt_ids[] = { + { .compatible = "fsl,imx8mp-aud2htx",}, + {} +}; + +MODULE_DEVICE_TABLE(of, fsl_aud2htx_dt_ids); + +static irqreturn_t fsl_aud2htx_isr(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} + +static int fsl_aud2htx_probe(struct platform_device *pdev) +{ + struct fsl_aud2htx *aud2htx; + struct resource *res; + struct device_node *np; + void __iomem *regs; + int ret, irq; + + aud2htx = devm_kzalloc(&pdev->dev, sizeof(*aud2htx), GFP_KERNEL); + if (!aud2htx) + return -ENOMEM; + + aud2htx->pdev = pdev; + np = pdev->dev.of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) { + dev_err(&pdev->dev, "failed ioremap\n"); + return PTR_ERR(regs); + } + + aud2htx->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, regs, + &fsl_aud2htx_regmap_config); + if (IS_ERR(aud2htx->regmap)) { + dev_err(&pdev->dev, "failed to init regmap"); + return PTR_ERR(aud2htx->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", + dev_name(&pdev->dev)); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_aud2htx_isr, 0, + dev_name(&pdev->dev), aud2htx); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret); + return ret; + } + + aud2htx->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(aud2htx->bus_clk)) { + dev_err(&pdev->dev, "failed to get mem clock\n"); + return PTR_ERR(aud2htx->bus_clk); + } + + aud2htx->dma_params_tx.chan_name = "tx"; + aud2htx->dma_params_tx.maxburst = 16; + aud2htx->dma_params_tx.addr = res->start + AUD2HTX_WR; + + platform_set_drvdata(pdev, aud2htx); + pm_runtime_enable(&pdev->dev); + + regcache_cache_only(aud2htx->regmap, true); + + ret = devm_snd_soc_register_component(&pdev->dev, + &fsl_aud2htx_component, + &fsl_aud2htx_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + ret = imx_pcm_platform_register(&pdev->dev); + if (ret) + dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + + return ret; +} + +static int fsl_aud2htx_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_aud2htx_runtime_suspend(struct device *dev) +{ + struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev); + + regcache_cache_only(aud2htx->regmap, true); + clk_disable_unprepare(aud2htx->bus_clk); + + return 0; +} + +static int fsl_aud2htx_runtime_resume(struct device *dev) +{ + struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(aud2htx->bus_clk); + if (ret) + return ret; + + regcache_cache_only(aud2htx->regmap, false); + regcache_mark_dirty(aud2htx->regmap); + regcache_sync(aud2htx->regmap); + + return 0; +} +#endif /*CONFIG_PM*/ + +static const struct dev_pm_ops fsl_aud2htx_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_aud2htx_runtime_suspend, + fsl_aud2htx_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver fsl_aud2htx_driver = { + .probe = fsl_aud2htx_probe, + .remove = fsl_aud2htx_remove, + .driver = { + .name = "fsl-aud2htx", + .pm = &fsl_aud2htx_pm_ops, + .of_match_table = fsl_aud2htx_dt_ids, + }, +}; +module_platform_driver(fsl_aud2htx_driver); + +MODULE_DESCRIPTION("NXP AUD2HTX driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_aud2htx.h b/sound/soc/fsl/fsl_aud2htx.h new file mode 100644 index 000000000000..8348e5e620cb --- /dev/null +++ b/sound/soc/fsl/fsl_aud2htx.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 NXP + */ + +#ifndef _FSL_AUD2HTX_H +#define _FSL_AUD2HTX_H + +#define FSL_AUD2HTX_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define AUD2HTX_CTRL 0x0 +#define AUD2HTX_CTRL_EXT 0x4 +#define AUD2HTX_WR 0x8 +#define AUD2HTX_STATUS 0xC +#define AUD2HTX_IRQ_NOMASK 0x10 +#define AUD2HTX_IRQ_MASKED 0x14 +#define AUD2HTX_IRQ_MASK 0x18 + +#define AUD2HTX_CTRL_EN BIT(0) +#define AUD2HTX_CTRE_DE BIT(0) +#define AUD2HTX_CTRE_DT_SHIFT 0x1 +#define AUD2HTX_CTRE_DT_WIDTH 0x2 +#define AUD2HTX_CTRE_DT_MASK ((BIT(AUD2HTX_CTRE_DT_WIDTH) - 1) \ + << AUD2HTX_CTRE_DT_SHIFT) + +#define AUD2HTX_CTRE_WL_SHIFT 16 +#define AUD2HTX_CTRE_WL_WIDTH 5 +#define AUD2HTX_CTRE_WL_MASK ((BIT(AUD2HTX_CTRE_WL_WIDTH) - 1) \ + << AUD2HTX_CTRE_WL_SHIFT) + +#define AUD2HTX_CTRE_WH_SHIFT 24 +#define AUD2HTX_CTRE_WH_WIDTH 5 +#define AUD2HTX_CTRE_WH_MASK ((BIT(AUD2HTX_CTRE_WH_WIDTH) - 1) \ + << AUD2HTX_CTRE_WH_SHIFT) + +#define AUD2HTX_WM_HIGH_IRQ_MASK BIT(2) +#define AUD2HTX_WM_LOW_IRQ_MASK BIT(1) +#define AUD2HTX_OVF_MASK BIT(0) + +struct fsl_aud2htx { + struct platform_device *pdev; + struct regmap *regmap; + struct clk *bus_clk; + + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +#endif /* _FSL_AUD2HTX_H */ diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c index 5faecbeb5497..78b776d96cf5 100644 --- a/sound/soc/fsl/fsl_audmix.c +++ b/sound/soc/fsl/fsl_audmix.c @@ -483,7 +483,7 @@ static int fsl_audmix_probe(struct platform_device *pdev) if (IS_ERR(regs)) return PTR_ERR(regs); - priv->regmap = devm_regmap_init_mmio_clk(dev, "ipg", regs, + priv->regmap = devm_regmap_init_mmio_clk(dev, NULL, regs, &fsl_audmix_regmap_config); if (IS_ERR(priv->regmap)) { dev_err(dev, "failed to init regmap\n"); diff --git a/sound/soc/fsl/fsl_dsd.h b/sound/soc/fsl/fsl_dsd.h new file mode 100644 index 000000000000..a45f31422f84 --- /dev/null +++ b/sound/soc/fsl/fsl_dsd.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 NXP + * + * 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 __FSL_DSD_H +#define __FSL_DSD_H + +#include <linux/pinctrl/consumer.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +static bool fsl_is_dsd(struct snd_pcm_hw_params *params) +{ + snd_pcm_format_t format = params_format(params); + + switch (format) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + return true; + default: + return false; + } +} + +static inline struct pinctrl_state *fsl_get_pins_state(struct pinctrl *pinctrl, + struct snd_pcm_hw_params *params, u32 bclk) +{ + struct pinctrl_state *state = 0; + + if (fsl_is_dsd(params)) { + /* DSD512@44.1kHz, DSD512@48kHz */ + if (bclk >= 22579200) + state = pinctrl_lookup_state(pinctrl, "dsd512"); + + /* Get default DSD state */ + if (IS_ERR_OR_NULL(state)) + state = pinctrl_lookup_state(pinctrl, "dsd"); + } else { + /* 706k32b2c, 768k32b2c, etc */ + if (bclk >= 45158400) + state = pinctrl_lookup_state(pinctrl, "pcm_b2m"); + } + + /* Get default state */ + if (IS_ERR_OR_NULL(state)) + state = pinctrl_lookup_state(pinctrl, "default"); + + return state; +} + +#endif /* __FSL_DSD_H */ diff --git a/sound/soc/fsl/fsl_dsp.c b/sound/soc/fsl/fsl_dsp.c new file mode 100644 index 000000000000..e64f9bb8af71 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp.c @@ -0,0 +1,1660 @@ +/* + * Freescale DSP driver + * + * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. + * Copyright 2018-2020 NXP + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Copyright (c) 2001 William L. Pitts + * All rights reserved. + * + * Redistribution and use in source and binary forms are freely + * permitted provided that the above copyright notice and this + * paragraph and the following disclaimer are duplicated in all + * such forms. + * + * This software is provided "AS IS" and without any express or + * implied warranties, including, without limitation, the implied + * warranties of merchantability and fitness for a particular + * purpose. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/file.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/slab.h> +#include <linux/platform_data/dma-imx.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/pm_runtime.h> +#include <linux/pm_domain.h> +#include <linux/mx8_mu.h> +#include <linux/uaccess.h> +#include <linux/poll.h> +#ifdef CONFIG_COMPAT +#include <linux/compat.h> +#endif +#include <uapi/linux/mxc_dsp.h> +#include <linux/firmware/imx/svc/misc.h> +#include <dt-bindings/firmware/imx/rsrc.h> + +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "fsl_dsp.h" +#include "fsl_dsp_pool.h" +#include "fsl_dsp_xaf_api.h" + +/* ...allocate new client */ +struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv) +{ + struct xf_client *client; + u32 id; + + id = dsp_priv->xf_client_map[0].next; + + /* ...try to allocate a client handle */ + if (id != 0) { + /* ...allocate client memory */ + client = kmalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + /* ...advance the head of free clients */ + dsp_priv->xf_client_map[0].next = + dsp_priv->xf_client_map[id].next; + + /* ...put associate client id with given object */ + dsp_priv->xf_client_map[id].client = client; + + /* ...mark client is not yet bound to proxy */ + client->proxy = NULL; + + /* ...save global proxy client identifier */ + client->id = id; + + return client; + } + + /* ...number of clients exceeded */ + return ERR_PTR(-EBUSY); +} + +/* ...recycle client object */ +static inline void xf_client_free(struct xf_client *client) +{ + int id = client->id; + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + + /* ...put proxy client id into free clients list */ + dsp_priv->xf_client_map[id].next = dsp_priv->xf_client_map[0].next; + dsp_priv->xf_client_map[0].next = id; + + /* ...destroy client data */ + kfree(client); +} + +/* ...lookup client basing on id */ +struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id) +{ + if ((id >= XF_CFG_MAX_IPC_CLIENTS) || + (dsp_priv->xf_client_map[id].next < XF_CFG_MAX_IPC_CLIENTS) + ) + return NULL; + else + return dsp_priv->xf_client_map[id].client; +} + +/* ...helper function for retrieving the client handle */ +static inline struct xf_client *xf_get_client(struct file *file) +{ + struct xf_client *client; + u32 id; + + client = (struct xf_client *)file->private_data; + if (!client) + return ERR_PTR(-EINVAL); + + id = client->id; + if (id >= XF_CFG_MAX_IPC_CLIENTS) + return ERR_PTR(-EINVAL); + + return client; +} + +static int fsl_dsp_client_register(struct xf_client *client) +{ + struct fsl_dsp *dsp_priv; + struct device *dev; + + dsp_priv = (struct fsl_dsp *)client->global; + dev = dsp_priv->dev; + + /* ...make sure client is not registered yet */ + if (client->proxy != NULL) { + pr_err("client-%x already registered", client->id); + return -EBUSY; + } + + /* ...complete association (no communication with remote proxy here) */ + client->proxy = &dsp_priv->proxy; + + pr_debug("client-%x registered within proxy", client->id); + + return 0; +} + +/* ...unregister client from shared memory interface */ +static int fsl_dsp_client_unregister(struct xf_client *client) +{ + struct xf_proxy *proxy = client->proxy; + + /* ...make sure client is registered */ + if (proxy == NULL) { + pr_err("client-%x is not registered", client->id); + return -EBUSY; + } + + /* ...just clean proxy reference */ + client->proxy = NULL; + + pr_debug("client-%x registered within proxy", client->id); + + return 0; +} + +static int fsl_dsp_ipc_msg_to_dsp(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct xf_proxy_message msg; + void *buffer; + unsigned long ret = 0; + + ret = copy_from_user(&msg, user, sizeof(struct xf_proxy_message)); + if (ret) { + dev_err(dev, "failed to get message from user space\n"); + return -EFAULT; + } + + /* ...make sure message pointer is sane */ + buffer = xf_proxy_a2b(&dsp_priv->proxy, msg.address); + if (buffer == (void *)-1) + return -EFAULT; + + /* ...put current proxy client into message session id */ + msg.session_id = XF_MSG_AP_FROM_USER(msg.session_id, client->id); + + xf_cmd_send(&dsp_priv->proxy, + msg.session_id, + msg.opcode, + buffer, + msg.length); + + return 0; +} + +static int fsl_dsp_ipc_msg_from_dsp(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct xf_message *m; + struct xf_proxy_message msg; + unsigned long ret = 0; + + m = xf_cmd_recv(&dsp_priv->proxy, &client->wait, &client->queue, 0); + if (IS_ERR(m)) { + xf_unlock(&dsp_priv->proxy.lock); + dev_err(dev, "receiving failed: %d", (int)PTR_ERR(m)); + return PTR_ERR(m); + } + + /* ...check if there is a response available */ + if (m == NULL) + return -EAGAIN; + + /* ...prepare message parameters (lock is taken) */ + msg.session_id = XF_MSG_AP_TO_USER(m->id); + msg.opcode = m->opcode; + msg.length = m->length; + msg.address = xf_proxy_b2a(&dsp_priv->proxy, m->buffer); + msg.ret = m->ret; + + /* ...return the message back to a pool and release lock */ + xf_msg_free(&dsp_priv->proxy, m); + xf_unlock(&dsp_priv->proxy.lock); + + ret = copy_to_user(user, &msg, sizeof(struct xf_proxy_message)); + if (ret) { + dev_err(dev, "failed to response message to user space\n"); + return -EFAULT; + } + + return 0; +} + +static int fsl_dsp_get_shmem_info(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct shmem_info mem_info; + unsigned long ret = 0; + + mem_info.phys_addr = dsp_priv->scratch_buf_phys; + mem_info.size = dsp_priv->scratch_buf_size; + + ret = copy_to_user(user, &mem_info, sizeof(struct shmem_info)); + if (ret) { + dev_err(dev, "failed to response message to user space\n"); + return -EFAULT; + } + + return ret; +} + +static struct miscdevice dsp_miscdev = { + .name = "mxc_hifi4", + .minor = MISC_DYNAMIC_MINOR, +}; + +static long fsl_dsp_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct xf_client *client; + struct fsl_dsp *dsp_priv; + struct xf_proxy *proxy; + struct device *dev; + void __user *user; + long ret = 0; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + dsp_priv = (struct fsl_dsp *)client->global; + proxy = &dsp_priv->proxy; + dev = dsp_priv->dev; + user = (void __user *)arg; + + mutex_lock(&dsp_priv->dsp_mutex); + + if (!proxy->is_ready) { + mutex_unlock(&dsp_priv->dsp_mutex); + dev_err(dev, "dsp firmware is not ready\n"); + return -EFAULT; + } + + switch (cmd) { + case DSP_CLIENT_REGISTER: + ret = fsl_dsp_client_register(client); + break; + case DSP_CLIENT_UNREGISTER: + ret = fsl_dsp_client_unregister(client); + break; + case DSP_IPC_MSG_SEND: + ret = fsl_dsp_ipc_msg_to_dsp(client, user); + break; + case DSP_IPC_MSG_RECV: + ret = fsl_dsp_ipc_msg_from_dsp(client, user); + break; + case DSP_GET_SHMEM_INFO: + ret = fsl_dsp_get_shmem_info(client, user); + break; + default: + break; + } + + mutex_unlock(&dsp_priv->dsp_mutex); + + return ret; +} + +void resource_release(struct fsl_dsp *dsp_priv) +{ + int i; + + /* ...initialize client association map */ + for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++) + dsp_priv->xf_client_map[i].next = i + 1; + /* ...set list terminator */ + dsp_priv->xf_client_map[i].next = 0; + + /* ...set pointer to shared memory */ + xf_proxy_init(&dsp_priv->proxy); +} + +int fsl_dsp_open_func(struct fsl_dsp *dsp_priv, struct xf_client *client) +{ + struct device *dev = dsp_priv->dev; + int ret = 0; + + /* ...initialize waiting queue */ + init_waitqueue_head(&client->wait); + + /* ...initialize client pending message queue */ + xf_msg_queue_init(&client->queue); + + /* ...mark user data is not mapped */ + client->vm_start = 0; + + /* ...reset mappings counter */ + atomic_set(&client->vm_use, 0); + + client->global = (void *)dsp_priv; + dsp_priv->proxy.is_active = 1; + + pm_runtime_get_sync(dev); + + mutex_lock(&dsp_priv->dsp_mutex); + /* increase reference counter when opening device */ + atomic_long_inc(&dsp_priv->refcnt); + mutex_unlock(&dsp_priv->dsp_mutex); + + return ret; +} + +static int fsl_dsp_open(struct inode *inode, struct file *file) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dsp_miscdev.parent); + struct xf_client *client; + int ret = 0; + + /* ...basic sanity checks */ + if (!inode || !file) + return -EINVAL; + + /* ...allocate new proxy client object */ + client = xf_client_alloc(dsp_priv); + if (IS_ERR(client)) + return PTR_ERR(client); + + fsl_dsp_open_func(dsp_priv, client); + + file->private_data = (void *)client; + + return ret; +} + +static int fsl_dsp_wait_idle(struct fsl_dsp *dsp_priv) +{ + int timeout = 200; + + if (dsp_priv->dsp_is_lpa) { + /* FW code is on OCRAM_A, Need wait DSP idle before gate */ + /* OCRAM_A clock. Or DSP will hang */ + while (!imx_audiomix_dsp_pwaitmode(dsp_priv->audiomix)) { + if (!timeout--) { + dev_err(dsp_priv->dev, "DSP failed to idle\n"); + return -ETIME; + } + udelay(5); + } + } + + return 0; +} + +int fsl_dsp_close_func(struct xf_client *client) +{ + struct fsl_dsp *dsp_priv; + struct device *dev; + struct xf_proxy *proxy; + + /* ...basic sanity checks */ + proxy = client->proxy; + + /* release all pending messages */ + if (proxy) + xf_msg_free_all(proxy, &client->queue); + + dsp_priv = (struct fsl_dsp *)client->global; + + /* wait until DSP idle */ + fsl_dsp_wait_idle(dsp_priv); + + dev = dsp_priv->dev; + pm_runtime_put_sync(dev); + + /* ...recycle client id and release memory */ + xf_client_free(client); + + mutex_lock(&dsp_priv->dsp_mutex); + /* decrease reference counter when closing device */ + atomic_long_dec(&dsp_priv->refcnt); + /* If device is free, reinitialize the resource of + * dsp driver and framework + */ + if (atomic_long_read(&dsp_priv->refcnt) <= 0) { + /* we are closing up, wait for proxy processing + * function to finish */ + cancel_work_sync(&dsp_priv->proxy.work); + resource_release(dsp_priv); + } + + mutex_unlock(&dsp_priv->dsp_mutex); + + return 0; +} + +static int fsl_dsp_close(struct inode *inode, struct file *file) +{ + struct xf_client *client; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + fsl_dsp_close_func(client); + + return 0; +} + +/* ...wait until data is available in the response queue */ +static unsigned int fsl_dsp_poll(struct file *file, poll_table *wait) +{ + struct xf_proxy *proxy; + struct xf_client *client; + int mask; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...get proxy interface */ + proxy = client->proxy; + if (!proxy) + return -EPERM; + + /* ...register client waiting queue */ + poll_wait(file, &client->wait, wait); + + /* ...return current queue state */ + mask = (xf_msg_queue_head(&client->queue) ? POLLIN | POLLRDNORM : 0); + + return mask; +} + +/******************************************************************************* + * Low-level mmap interface + ******************************************************************************/ + +/* ...add reference to shared buffer */ +static void dsp_mmap_open(struct vm_area_struct *vma) +{ + struct xf_client *client = vma->vm_private_data; + + /* ...probably just increase counter of open references? - tbd */ + atomic_inc(&client->vm_use); + + pr_debug("xf_mmap_open: vma = %p, client = %p", vma, client); +} + +/* ...close reference to shared buffer */ +static void dsp_mmap_close(struct vm_area_struct *vma) +{ + struct xf_client *client = vma->vm_private_data; + + pr_debug("xf_mmap_close: vma = %p, b = %p", vma, client); + + /* ...decrement number of mapping */ + atomic_dec(&client->vm_use); +} + +/* ...memory map operations */ +static const struct vm_operations_struct dsp_mmap_ops = { + .open = dsp_mmap_open, + .close = dsp_mmap_close, +}; + +/* ...shared memory mapping */ +static int fsl_dsp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct xf_proxy *proxy; + struct xf_client *client; + unsigned long size; + unsigned long pfn; + int r; + struct fsl_dsp *dsp_priv; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...get proxy interface */ + proxy = client->proxy; + if (!proxy) + return -EPERM; + + /* ...check it was not mapped already */ + if (client->vm_start != 0) + return -EBUSY; + + /* ...check mapping flags (tbd) */ + if ((vma->vm_flags & (VM_READ | VM_WRITE | VM_SHARED)) + != (VM_READ | VM_WRITE | VM_SHARED)) + return -EPERM; + + /* ...set memory map operations */ + vma->vm_ops = &dsp_mmap_ops; + + /* ...assign private data */ + client->vm_start = vma->vm_start; + + /* ...set private memory data */ + vma->vm_private_data = client; + + /* ...set page number of shared memory */ + dsp_priv = (struct fsl_dsp *)client->global; + pfn = dsp_priv->scratch_buf_phys >> PAGE_SHIFT; + size = dsp_priv->scratch_buf_size; + + /* ...remap shared memory to user-space */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); + if (r != 0) { + pr_err("mapping failed: %d", r); + return r; + } + + /* ...system-specific hook for registering shared memory mapping */ + return 0; +} + +void *memset_dsp(void *dest, int c, size_t count) +{ + uint *dl = (uint *)dest; + void *dl_1, *dl_2; + size_t align = 4; + size_t n, n1, n2; + + /* while all data is aligned (common case), copy a word at a time */ + if ((((ulong)dest) & (sizeof(*dl) - 1)) != 0) { + dl = (unsigned int *)(((ulong)dest + align - 1) & + (~(align - 1))); + dl_1 = dest; + dl_2 = (void *)(((ulong)dest + count) & (~(align - 1))); + n1 = (ulong)dl - (ulong)dl_1; + n2 = (ulong)dest + count - (ulong)dl_2; + n = (count - n1 - n2) / align; + + while (n--) { + writel_relaxed(0, dl); + dl++; + } + while (n1--) { + writeb_relaxed(0, dl_1); + dl_1++; + } + while (n2--) { + writeb_relaxed(0, dl_2); + dl_2++; + } + } else { + n = count / align; + n1 = count - n * align; + dl_1 = dest + n * align; + while (n--) { + writel_relaxed(0, dl); + dl++; + } + while (n1--) { + writeb_relaxed(0, dl_1); + dl_1++; + } + } + + return dest; +} + +void *memcpy_dsp(void *dest, const void *src, size_t count) +{ + unsigned int *dl = (unsigned int *)dest, *sl = (unsigned int *)src; + size_t n = round_up(count, 4) / 4; + + if (src == dest) + return dest; + + /* while all data is aligned (common case), copy a word at a time */ + if ((((ulong)dest | (ulong)src) & (sizeof(*dl) - 1)) != 0) + pr_info("dest %p src %p not 4 bytes aligned\n", dest, src); + + while (n--) { + writel_relaxed(*sl, dl); + dl++; + sl++; + } + + return dest; +} + +static void fsl_dsp_start(struct fsl_dsp *dsp_priv) +{ + switch (dsp_priv->dsp_board_type){ + case DSP_IMX8QM_TYPE: + case DSP_IMX8QXP_TYPE: +#if defined(CONFIG_IMX_SCU) + imx_sc_pm_cpu_start(dsp_priv->dsp_ipcHandle, + IMX_SC_R_DSP, true, dsp_priv->iram); +#endif + break; + case DSP_IMX8MP_TYPE: + imx_audiomix_dsp_start(dsp_priv->audiomix); + break; + default: + break; + } +} + +static bool fsl_dsp_is_reset(struct fsl_dsp *dsp_priv) +{ + switch (dsp_priv->dsp_board_type) { + case DSP_IMX8QM_TYPE: + case DSP_IMX8QXP_TYPE: + return true; + case DSP_IMX8MP_TYPE: + return imx_audiomix_dsp_reset(dsp_priv->audiomix); + default: + return true; + } +} + +static void dsp_load_firmware(const struct firmware *fw, void *context) +{ + struct fsl_dsp *dsp_priv = context; + struct device *dev = dsp_priv->dev; + Elf32_Ehdr *ehdr; /* Elf header structure pointer */ + Elf32_Shdr *shdr; /* Section header structure pointer */ + Elf32_Addr sh_addr; + unsigned char *strtab = 0; /* String table pointer */ + unsigned char *image; /* Binary image pointer */ + int i; /* Loop counter */ + unsigned long addr; + + if (!fw) { + dev_info(dev, "external firmware not found\n"); + return; + } + + addr = (unsigned long)fw->data; + ehdr = (Elf32_Ehdr *)addr; + + /* Find the section header string table for output info */ + shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff + + (ehdr->e_shstrndx * sizeof(Elf32_Shdr))); + + strtab = (unsigned char *)(addr + shdr->sh_offset); + + /* Load each appropriate section */ + for (i = 0; i < ehdr->e_shnum; ++i) { + shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff + + (i * sizeof(Elf32_Shdr))); + + if (!(shdr->sh_flags & SHF_ALLOC) || + shdr->sh_addr == 0 || shdr->sh_size == 0) + continue; + + dev_dbg(dev, "%sing %s @ 0x%08lx (%ld bytes)\n", + (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load", + &strtab[shdr->sh_name], (unsigned long)shdr->sh_addr, + (long)shdr->sh_size); + + sh_addr = shdr->sh_addr; + + if (shdr->sh_type == SHT_NOBITS) { + memset_dsp((void *)(dsp_priv->sdram_vir_addr + + (sh_addr - dsp_priv->sdram_phys_addr)), + 0, + shdr->sh_size); + } else { + image = (unsigned char *)addr + shdr->sh_offset; + if ((!strcmp(&strtab[shdr->sh_name], ".rodata")) || + (!strcmp(&strtab[shdr->sh_name], ".text")) || + (!strcmp(&strtab[shdr->sh_name], ".data")) || + (!strcmp(&strtab[shdr->sh_name], ".bss")) || + (!strcmp(&strtab[shdr->sh_name], ".rtos.rodata")) || + (!strcmp(&strtab[shdr->sh_name], ".clib.data")) || + (!strcmp(&strtab[shdr->sh_name], ".rtos.percpu.data")) + ) { + memcpy_dsp((void *)(dsp_priv->sdram_vir_addr + + (sh_addr - dsp_priv->sdram_phys_addr)), + (const void *)image, + shdr->sh_size); + } else { + /* sh_addr is from DSP view, we need to + * fixup addr because we load the firmware from + * the ARM core side + */ + sh_addr -= dsp_priv->fixup_offset; + + memcpy_dsp((void *)(dsp_priv->regs + + (sh_addr - dsp_priv->paddr)), + (const void *)image, + shdr->sh_size); + } + } + } + + /* start the core */ + fsl_dsp_start(dsp_priv); +} + +/* Initialization of the MU code. */ +int dsp_mu_init(struct fsl_dsp *dsp_priv) +{ + struct device *dev = dsp_priv->dev; + struct device_node *np; + unsigned int dsp_mu_id; + u32 irq; + int ret = 0; + + /* + * Get the address of MU to be used for communication with the dsp + */ + np = of_find_compatible_node(NULL, NULL, "fsl,imx8-mu-dsp"); + if (!np) { + dev_err(dev, "Cannot find MU entry in device tree\n"); + return -EINVAL; + } + dsp_priv->mu_base_virtaddr = of_iomap(np, 0); + WARN_ON(!dsp_priv->mu_base_virtaddr); + + ret = of_property_read_u32_index(np, + "fsl,dsp_ap_mu_id", 0, &dsp_mu_id); + if (ret) { + dev_err(dev, "Cannot get mu_id %d\n", ret); + return -EINVAL; + } + + dsp_priv->dsp_mu_id = dsp_mu_id; + + irq = of_irq_get(np, 0); + + ret = devm_request_irq(dsp_priv->dev, irq, fsl_dsp_mu_isr, + IRQF_EARLY_RESUME, "dsp_mu_isr", &dsp_priv->proxy); + if (ret) { + dev_err(dev, "request_irq failed %d, err = %d\n", irq, ret); + return -EINVAL; + } + + if (dsp_priv->dsp_is_lpa) { + ret = irq_set_irq_wake(irq, 1); + if (ret) { + dev_err(dev, "Failed to set IRQ %d as wake source: %d\n", + irq, ret); + return ret; + } + } + + return ret; +} + +static const struct file_operations dsp_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_dsp_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = fsl_dsp_ioctl, +#endif + .open = fsl_dsp_open, + .poll = fsl_dsp_poll, + .mmap = fsl_dsp_mmap, + .release = fsl_dsp_close, +}; + +extern struct snd_compr_ops dsp_platform_compr_lpa_ops; + +static const struct snd_soc_component_driver dsp_soc_platform_lpa_drv = { + .name = FSL_DSP_COMP_NAME, + .compr_ops = &dsp_platform_compr_lpa_ops, +}; + +extern struct snd_compr_ops dsp_platform_compr_ops; + +static const struct snd_soc_component_driver dsp_soc_platform_drv = { + .name = FSL_DSP_COMP_NAME, + .compr_ops = &dsp_platform_compr_ops, +}; + + +int fsl_dsp_configure_audmix(struct fsl_dsp *dsp_priv) { + struct device_node *np; + struct platform_device *pdev; + + np = of_find_node_by_name(NULL, "audiomix_dsp"); + if (!np) + return -EPROBE_DEFER; + + pdev = of_find_device_by_node(np); + if (!pdev) + return -EPROBE_DEFER; + + dsp_priv->audiomix = dev_get_drvdata(&pdev->dev); + if (!dsp_priv->audiomix) + return -EPROBE_DEFER; + + return 0; +} + +int fsl_dsp_configure_scu(struct fsl_dsp *dsp_priv) +{ + int ret; + + /* there is no SCU on i.MX8MP */ + if (dsp_priv->dsp_board_type == DSP_IMX8MP_TYPE) + return 0; + + ret = imx_scu_get_handle(&dsp_priv->dsp_ipcHandle); + if (ret) { + dev_err(dsp_priv->dev, "Cannot get scu handle %d\n", ret); + return ret; + } + + if (dsp_priv->dsp_board_type == DSP_IMX8QXP_TYPE) { + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 1); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset source select\n"); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_PERIPH, 0x5A); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset of PERIPH %d\n", + ret); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_IRQ, 0x51); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset of IRQ\n"); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_AUDIO, 0x80); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset of AUDIO\n"); + return -EIO; + } + } else { + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 0); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset source select\n"); + return -EIO; + } + } + + return 0; +} + +int fsl_dsp_configure(struct fsl_dsp *dsp_priv) +{ + switch (dsp_priv->dsp_board_type) { + case DSP_IMX8QM_TYPE: + case DSP_IMX8QXP_TYPE: + return fsl_dsp_configure_scu(dsp_priv); + case DSP_IMX8MP_TYPE: + return fsl_dsp_configure_audmix(dsp_priv); + default: + return -ENODEV; + } +} + +/** + * fsl_dsp_attach_pm_domains + */ +static int fsl_dsp_attach_pm_domains(struct device *dev, + struct fsl_dsp *dsp) +{ + int ret; + int i; + + if (dsp->num_domains <= 1) + return 0; + + dsp->pd_dev = devm_kmalloc_array(dev, dsp->num_domains, + sizeof(*dsp->pd_dev), + GFP_KERNEL); + if (!dsp->pd_dev) + return -ENOMEM; + + dsp->pd_dev_link = devm_kmalloc_array(dev, + dsp->num_domains, + sizeof(*dsp->pd_dev_link), + GFP_KERNEL); + if (!dsp->pd_dev_link) + return -ENOMEM; + + for (i = 0; i < dsp->num_domains; i++) { + dsp->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(dsp->pd_dev[i])) + return PTR_ERR(dsp->pd_dev[i]); + + dsp->pd_dev_link[i] = device_link_add(dev, + dsp->pd_dev[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (IS_ERR(dsp->pd_dev_link[i])) { + dev_pm_domain_detach(dsp->pd_dev[i], false); + ret = PTR_ERR(dsp->pd_dev_link[i]); + goto detach_pm; + } + } + return 0; + +detach_pm: + while (--i >= 0) { + device_link_del(dsp->pd_dev_link[i]); + dev_pm_domain_detach(dsp->pd_dev[i], false); + } + return ret; +} + +/** + * fsl_dsp_detach_pm_domains + */ +static int fsl_dsp_detach_pm_domains(struct device *dev, + struct fsl_dsp *dsp) +{ + int i; + + if (dsp->num_domains <= 1) + return 0; + + for (i = 0; i < dsp->num_domains; i++) { + device_link_del(dsp->pd_dev_link[i]); + dev_pm_domain_detach(dsp->pd_dev[i], false); + } + + return 0; +} + +static int fsl_dsp_mem_setup_lpa(struct fsl_dsp *dsp_priv) +{ + struct device_node *np = dsp_priv->dev->of_node; + struct device_node *reserved_node; + struct resource reserved_res; + int offset, size; + + reserved_node = of_parse_phandle(np, "ocram", 0); + if (!reserved_node) { + dev_err(dsp_priv->dev, "failed to get reserved region node\n"); + return -ENODEV; + } + + if (of_address_to_resource(reserved_node, 0, &reserved_res)) { + dev_err(dsp_priv->dev, "failed to get reserved region address\n"); + return -EINVAL; + } + + dsp_priv->ocram_phys_addr = reserved_res.start; + dsp_priv->ocram_reserved_size = (reserved_res.end - reserved_res.start) + + 1; + if (dsp_priv->ocram_reserved_size <= 0) { + dev_err(dsp_priv->dev, "invalid value of reserved region size\n"); + return -EINVAL; + } + + dsp_priv->ocram_vir_addr = ioremap_wc(dsp_priv->ocram_phys_addr, + dsp_priv->ocram_reserved_size); + if (!dsp_priv->ocram_vir_addr) { + dev_err(dsp_priv->dev, "failed to remap ocram space for dsp firmware\n"); + return -ENXIO; + } + memset_io(dsp_priv->ocram_vir_addr, 0, dsp_priv->ocram_reserved_size); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + + /* msg ring buffer memory */ + dsp_priv->msg_buf_virt = dsp_priv->ocram_vir_addr + dsp_priv->ocram_reserved_size - size; + dsp_priv->msg_buf_phys = dsp_priv->ocram_phys_addr + dsp_priv->ocram_reserved_size - size; + dsp_priv->msg_buf_size = MSG_BUF_SIZE; + offset = MSG_BUF_SIZE; + + /* keep dsp framework's global data when suspend/resume */ + dsp_priv->dsp_config_virt = dsp_priv->ocram_vir_addr + dsp_priv->ocram_reserved_size - size + offset; + dsp_priv->dsp_config_phys = dsp_priv->ocram_phys_addr + dsp_priv->ocram_reserved_size - size + offset; + dsp_priv->dsp_config_size = DSP_CONFIG_SIZE; + + dsp_priv->scratch_buf_virt = dsp_priv->ocram_vir_addr; + dsp_priv->scratch_buf_phys = dsp_priv->ocram_phys_addr; + dsp_priv->scratch_buf_size = dsp_priv->ocram_reserved_size - size; + dsp_priv->dram_reserved_vir_addr = dsp_priv->sdram_vir_addr; + dsp_priv->dram_reserved_phys_addr = dsp_priv->sdram_phys_addr; + dsp_priv->dram_reserved_size = dsp_priv->sdram_reserved_size; + dsp_priv->sdram_vir_addr = dsp_priv->regs + SYSRAM_OFFSET; + dsp_priv->sdram_phys_addr = dsp_priv->paddr + SYSRAM_OFFSET; + dsp_priv->sdram_reserved_size = SYSRAM_SIZE; + + return 0; +} + +static int fsl_dsp_mem_setup(struct fsl_dsp *dsp_priv) +{ + void *buf_virt; + dma_addr_t buf_phys; + int size, offset; + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + buf_virt = dma_alloc_coherent(dsp_priv->dev, size, &buf_phys, GFP_KERNEL); + if (!buf_virt) { + dev_err(dsp_priv->dev, "failed alloc memory.\n"); + return -ENOMEM; + } + + /* msg ring buffer memory */ + dsp_priv->msg_buf_virt = buf_virt; + dsp_priv->msg_buf_phys = buf_phys; + dsp_priv->msg_buf_size = MSG_BUF_SIZE; + offset = MSG_BUF_SIZE; + + /* keep dsp framework's global data when suspend/resume */ + dsp_priv->dsp_config_virt = buf_virt + offset; + dsp_priv->dsp_config_phys = buf_phys + offset; + dsp_priv->dsp_config_size = DSP_CONFIG_SIZE; + + /* scratch memory for dsp framework. The sdram reserved memory + * is split into two equal parts currently. The front part is + * used to keep the dsp firmware, the other part is considered + * as scratch memory for dsp framework. + */ + dsp_priv->scratch_buf_virt = dsp_priv->sdram_vir_addr + + dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_phys = dsp_priv->sdram_phys_addr + + dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_size = dsp_priv->sdram_reserved_size / 2; + + return 0; +} + +static int fsl_dsp_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *reserved_node; + struct resource reserved_res; + struct fsl_dsp *dsp_priv; + const char *fw_name; + const char *audio_iface; + struct resource *res; + void __iomem *regs; + int size, i; + int ret; + char tmp[16]; + + dsp_priv = devm_kzalloc(&pdev->dev, sizeof(*dsp_priv), GFP_KERNEL); + if (!dsp_priv) + return -ENOMEM; + + if (of_device_is_compatible(np, "fsl,imx8qxp-dsp-v1")) + dsp_priv->dsp_board_type = DSP_IMX8QXP_TYPE; + else if (of_device_is_compatible(np, "fsl,imx8qm-dsp-v1")) + dsp_priv->dsp_board_type = DSP_IMX8QM_TYPE; + else + dsp_priv->dsp_board_type = DSP_IMX8MP_TYPE; + + if (of_device_is_compatible(np, "fsl,imx8mp-dsp-lpa")) { + dsp_priv->dsp_board_type = DSP_IMX8MP_TYPE; + dsp_priv->dsp_is_lpa = 1; + } + + dsp_priv->dev = &pdev->dev; + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dsp_priv->paddr = res->start; + dsp_priv->regs = regs; + + dsp_priv->dram0 = dsp_priv->paddr + DRAM0_OFFSET; + dsp_priv->dram1 = dsp_priv->paddr + DRAM1_OFFSET; + dsp_priv->iram = dsp_priv->paddr + IRAM_OFFSET; + dsp_priv->sram = dsp_priv->paddr + SYSRAM_OFFSET; + + dsp_priv->num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + ret = fsl_dsp_attach_pm_domains(&pdev->dev, dsp_priv); + if (ret) + return ret; + + platform_set_drvdata(pdev, dsp_priv); + pm_runtime_enable(&pdev->dev); + dsp_priv->dsp_mu_init = 1; + dsp_priv->proxy.is_ready = 1; + pm_runtime_get_sync(&pdev->dev); + + ret = fsl_dsp_configure(dsp_priv); + if (ret < 0) { + pm_runtime_put_sync(&pdev->dev); + goto configure_fail; + } + + ret = dsp_mu_init(dsp_priv); + if (ret) { + pm_runtime_put_sync(&pdev->dev); + goto mu_init_fail; + } + + pm_runtime_put_sync(&pdev->dev); + dsp_priv->dsp_mu_init = 0; + dsp_priv->proxy.is_ready = 0; + + ret = of_property_read_string(np, "fsl,dsp-firmware", &fw_name); + dsp_priv->fw_name = fw_name; + + ret = of_property_read_string(np, "audio-interface", &audio_iface); + dsp_priv->audio_iface = audio_iface; + + ret = of_property_read_u32(np, "fixup-offset", &dsp_priv->fixup_offset); + + if (!dsp_priv->dsp_is_lpa) { + dsp_miscdev.fops = &dsp_fops, + dsp_miscdev.parent = &pdev->dev, + ret = misc_register(&dsp_miscdev); + if (ret) { + dev_err(&pdev->dev, "failed to register misc device %d\n", ret); + goto misc_register_fail; + } + } + + reserved_node = of_parse_phandle(np, "memory-region", 0); + if (!reserved_node) { + dev_err(&pdev->dev, "failed to get reserved region node\n"); + ret = -ENODEV; + goto reserved_node_fail; + } + + if (of_address_to_resource(reserved_node, 0, &reserved_res)) { + dev_err(&pdev->dev, "failed to get reserved region address\n"); + ret = -EINVAL; + goto reserved_node_fail; + } + + dsp_priv->sdram_phys_addr = reserved_res.start; + dsp_priv->sdram_reserved_size = (reserved_res.end - reserved_res.start) + + 1; + if (dsp_priv->sdram_reserved_size <= 0) { + dev_err(&pdev->dev, "invalid value of reserved region size\n"); + ret = -EINVAL; + goto reserved_node_fail; + } + + dsp_priv->sdram_vir_addr = ioremap_wc(dsp_priv->sdram_phys_addr, + dsp_priv->sdram_reserved_size); + if (!dsp_priv->sdram_vir_addr) { + dev_err(&pdev->dev, "failed to remap sdram space for dsp firmware\n"); + ret = -ENXIO; + goto reserved_node_fail; + } + memset_io(dsp_priv->sdram_vir_addr, 0, dsp_priv->sdram_reserved_size); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + + if (dsp_priv->dsp_is_lpa) { + ret = fsl_dsp_mem_setup_lpa(dsp_priv); + if (ret) { + dev_err(&pdev->dev, "lpa mem setup fail.\n"); + goto reserved_node_fail; + } + } else { + if (fsl_dsp_mem_setup(dsp_priv)) { + dev_err(&pdev->dev, "failed alloc memory.\n"); + ret = -ENOMEM; + goto alloc_coherent_fail; + } + } + + /* initialize the reference counter for dsp_priv + * structure + */ + atomic_long_set(&dsp_priv->refcnt, 0); + + /* ...initialize client association map */ + for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++) + dsp_priv->xf_client_map[i].next = i + 1; + /* ...set list terminator */ + dsp_priv->xf_client_map[i].next = 0; + + /* ...set pointer to shared memory */ + xf_proxy_init(&dsp_priv->proxy); + + /* ...initialize mutex */ + mutex_init(&dsp_priv->dsp_mutex); + + if (dsp_priv->dsp_is_lpa) { + ret = devm_snd_soc_register_component(&pdev->dev, &dsp_soc_platform_lpa_drv, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "registering soc platform failed\n"); + goto register_component_fail; + } + } else { + ret = devm_snd_soc_register_component(&pdev->dev, &dsp_soc_platform_drv, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "registering soc platform failed\n"); + goto register_component_fail; + } + } + + dsp_priv->esai_ipg_clk = devm_clk_get(&pdev->dev, "esai_ipg"); + if (IS_ERR(dsp_priv->esai_ipg_clk)) + dsp_priv->esai_ipg_clk = NULL; + + dsp_priv->esai_mclk = devm_clk_get(&pdev->dev, "esai_mclk"); + if (IS_ERR(dsp_priv->esai_mclk)) + dsp_priv->esai_mclk = NULL; + + dsp_priv->asrc_mem_clk = devm_clk_get(&pdev->dev, "asrc_mem"); + if (IS_ERR(dsp_priv->asrc_mem_clk)) + dsp_priv->asrc_mem_clk = NULL; + + dsp_priv->asrc_ipg_clk = devm_clk_get(&pdev->dev, "asrc_ipg"); + if (IS_ERR(dsp_priv->asrc_ipg_clk)) + dsp_priv->asrc_ipg_clk = NULL; + + for (i = 0; i < 4; i++) { + sprintf(tmp, "asrck_%x", i); + dsp_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(dsp_priv->asrck_clk[i])) + dsp_priv->asrck_clk[i] = NULL; + } + + dsp_priv->dsp_ocrama_clk = devm_clk_get(&pdev->dev, "ocram"); + if (IS_ERR(dsp_priv->dsp_ocrama_clk)) + dsp_priv->dsp_ocrama_clk = NULL; + + dsp_priv->audio_root_clk = devm_clk_get(&pdev->dev, "audio_root"); + if (IS_ERR(dsp_priv->audio_root_clk)) + dsp_priv->audio_root_clk = NULL; + + dsp_priv->audio_axi_clk = devm_clk_get(&pdev->dev, "audio_axi"); + if (IS_ERR(dsp_priv->audio_axi_clk)) + dsp_priv->audio_axi_clk = NULL; + + dsp_priv->dsp_root_clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(dsp_priv->dsp_root_clk)) + dsp_priv->dsp_root_clk = NULL; + + dsp_priv->debug_clk = devm_clk_get(&pdev->dev, "debug"); + if (IS_ERR(dsp_priv->debug_clk)) + dsp_priv->debug_clk = NULL; + + dsp_priv->mu2_clk = devm_clk_get(&pdev->dev, "mu2"); + if (IS_ERR(dsp_priv->mu2_clk)) + dsp_priv->mu2_clk = NULL; + + dsp_priv->sdma_root_clk = devm_clk_get(&pdev->dev, "sdma_root"); + if (IS_ERR(dsp_priv->sdma_root_clk)) + dsp_priv->sdma_root_clk = NULL; + dsp_priv->sai_ipg_clk = devm_clk_get(&pdev->dev, "sai_ipg"); + if (IS_ERR(dsp_priv->sai_ipg_clk)) + dsp_priv->sai_ipg_clk = NULL; + dsp_priv->sai_mclk = devm_clk_get(&pdev->dev, "sai_mclk"); + if (IS_ERR(dsp_priv->sai_mclk)) + dsp_priv->sai_mclk = NULL; + dsp_priv->uart_ipg_clk = devm_clk_get(&pdev->dev, "uart_ipg"); + if (IS_ERR(dsp_priv->uart_ipg_clk)) + dsp_priv->uart_ipg_clk = NULL; + dsp_priv->uart_per_clk = devm_clk_get(&pdev->dev, "uart_per"); + if (IS_ERR(dsp_priv->uart_per_clk)) + dsp_priv->uart_per_clk = NULL; + + return 0; + +register_component_fail: + dma_free_coherent(&pdev->dev, size, dsp_priv->msg_buf_virt, + dsp_priv->msg_buf_phys); +alloc_coherent_fail: + if (dsp_priv->sdram_vir_addr) + iounmap(dsp_priv->sdram_vir_addr); + if (dsp_priv->ocram_vir_addr) + iounmap(dsp_priv->ocram_vir_addr); + +reserved_node_fail: + if (!dsp_priv->dsp_is_lpa) + misc_deregister(&dsp_miscdev); +misc_register_fail: +mu_init_fail: +configure_fail: + pm_runtime_disable(&pdev->dev); + fsl_dsp_detach_pm_domains(&pdev->dev, dsp_priv); + return ret; +} + +static int fsl_dsp_remove(struct platform_device *pdev) +{ + struct fsl_dsp *dsp_priv = platform_get_drvdata(pdev); + int size; + + if (!dsp_priv->dsp_is_lpa) + misc_deregister(&dsp_miscdev); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + dma_free_coherent(&pdev->dev, size, dsp_priv->msg_buf_virt, + dsp_priv->msg_buf_phys); + if (dsp_priv->sdram_vir_addr) + iounmap(dsp_priv->sdram_vir_addr); + if (dsp_priv->ocram_vir_addr) + iounmap(dsp_priv->ocram_vir_addr); + + pm_runtime_disable(&pdev->dev); + fsl_dsp_detach_pm_domains(&pdev->dev, dsp_priv); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_dsp_runtime_resume(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret; + int i; + + ret = clk_prepare_enable(dsp_priv->esai_ipg_clk); + if (ret) { + dev_err(dev, "failed to enable esai ipg clock: %d\n", ret); + goto esai_ipg_clk; + } + + ret = clk_prepare_enable(dsp_priv->esai_mclk); + if (ret) { + dev_err(dev, "failed to enable esai mclk: %d\n", ret); + goto esai_mclk; + } + + ret = clk_prepare_enable(dsp_priv->asrc_mem_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable asrc_mem_clk ret = %d\n", ret); + goto asrc_mem_clk; + } + + ret = clk_prepare_enable(dsp_priv->asrc_ipg_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable asrc_ipg_clk ret = %d\n", ret); + goto asrc_ipg_clk; + } + + for (i = 0; i < 4; i++) { + ret = clk_prepare_enable(dsp_priv->asrck_clk[i]); + if (ret < 0) { + dev_err(dev, "failed to prepare arc clk %d\n", i); + goto asrck_clk; + } + } + + ret = clk_prepare_enable(dsp_priv->dsp_ocrama_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable dsp_ocrama_clk ret = %d\n", ret); + goto ocrama_clk; + } + + ret = clk_prepare_enable(dsp_priv->dsp_root_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable dsp_root_clk ret = %d\n", ret); + goto dsp_root_clk; + } + + ret = clk_prepare_enable(dsp_priv->audio_root_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable audio_root_clk ret = %d\n", ret); + goto audio_root_clk; + } + + ret = clk_prepare_enable(dsp_priv->audio_axi_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable audio_axi_clk ret = %d\n", ret); + goto audio_axi_clk; + } + + ret = clk_prepare_enable(dsp_priv->debug_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable debug_clk ret = %d\n", ret); + goto debug_clk; + } + + ret = clk_prepare_enable(dsp_priv->mu2_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable mu2_clk ret = %d\n", ret); + goto mu2_clk; + } + + ret = clk_prepare_enable(dsp_priv->sdma_root_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable sdma_root _clk ret = %d\n", ret); + return ret; + } + ret = clk_prepare_enable(dsp_priv->sai_ipg_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable sai_ipg_clk ret = %d\n", ret); + return ret; + } + ret = clk_prepare_enable(dsp_priv->sai_mclk); + if (ret < 0) { + dev_err(dev, "Failed to enable sai_mclk ret = %d\n", ret); + return ret; + } + ret = clk_prepare_enable(dsp_priv->uart_ipg_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable uart_ipg_clk ret = %d\n", ret); + return ret; + } + ret = clk_prepare_enable(dsp_priv->uart_per_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable uart_per_clk ret = %d\n", ret); + return ret; + } + + if (!dsp_priv->dsp_mu_init && !proxy->is_ready && !fsl_dsp_is_reset(dsp_priv)) { + dsp_priv->dsp_mu_init = 1; + proxy->is_ready = 1; + } + + /* + * Use PID for checking the audiomix is reset or not. + * After resetting, the PID should be 0, then we set the PID=1 in resume. + */ + if (!dsp_priv->dsp_mu_init && !proxy->is_ready && dsp_priv->dsp_board_type == DSP_IMX8MP_TYPE) + imx_audiomix_dsp_pid_set(dsp_priv->audiomix, 0x1); + + if (!dsp_priv->dsp_mu_init) { + MU_Init(dsp_priv->mu_base_virtaddr); + MU_EnableRxFullInt(dsp_priv->mu_base_virtaddr, 0); + dsp_priv->dsp_mu_init = 1; + } + + if (!proxy->is_ready) { + init_completion(&proxy->cmd_complete); + + ret = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, dsp_priv->fw_name, + dev, + GFP_KERNEL, dsp_priv, dsp_load_firmware); + + if (ret) { + dev_err(dev, "failed to load firmware\n"); + return ret; + } + + ret = icm_ack_wait(proxy, 0); + if (ret) + return ret; + + dev_info(dev, "dsp driver registered\n"); + } + + return 0; + +mu2_clk: + clk_disable_unprepare(dsp_priv->debug_clk); +debug_clk: + clk_disable_unprepare(dsp_priv->audio_axi_clk); +audio_axi_clk: + clk_disable_unprepare(dsp_priv->audio_root_clk); +audio_root_clk: + clk_disable_unprepare(dsp_priv->dsp_root_clk); +dsp_root_clk: + clk_disable_unprepare(dsp_priv->dsp_ocrama_clk); +ocrama_clk: + for (i = 0; i < 4; i++) + clk_disable_unprepare(dsp_priv->asrck_clk[i]); +asrck_clk: + clk_disable_unprepare(dsp_priv->asrc_ipg_clk); +asrc_ipg_clk: + clk_disable_unprepare(dsp_priv->asrc_mem_clk); +asrc_mem_clk: + clk_disable_unprepare(dsp_priv->esai_mclk); +esai_mclk: + clk_disable_unprepare(dsp_priv->esai_ipg_clk); +esai_ipg_clk: + return ret; +} + +static int fsl_dsp_runtime_suspend(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int i; + + dsp_priv->dsp_mu_init = 0; + proxy->is_ready = 0; + + for (i = 0; i < 4; i++) + clk_disable_unprepare(dsp_priv->asrck_clk[i]); + + clk_disable_unprepare(dsp_priv->asrc_ipg_clk); + clk_disable_unprepare(dsp_priv->asrc_mem_clk); + + clk_disable_unprepare(dsp_priv->esai_mclk); + clk_disable_unprepare(dsp_priv->esai_ipg_clk); + + clk_disable_unprepare(dsp_priv->dsp_ocrama_clk); + clk_disable_unprepare(dsp_priv->dsp_root_clk); + clk_disable_unprepare(dsp_priv->audio_root_clk); + clk_disable_unprepare(dsp_priv->audio_axi_clk); + clk_disable_unprepare(dsp_priv->debug_clk); + clk_disable_unprepare(dsp_priv->mu2_clk); + clk_disable_unprepare(dsp_priv->sdma_root_clk); + clk_disable_unprepare(dsp_priv->sai_ipg_clk); + clk_disable_unprepare(dsp_priv->sai_mclk); + clk_disable_unprepare(dsp_priv->uart_ipg_clk); + clk_disable_unprepare(dsp_priv->uart_per_clk); + + return 0; +} +#endif /* CONFIG_PM */ + + +#ifdef CONFIG_PM_SLEEP +static int fsl_dsp_suspend(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret = 0; + + if (dsp_priv->dsp_is_lpa) + return ret; + + if (proxy->is_ready & pm_runtime_active(dev)) { + ret = xf_cmd_send_suspend(proxy); + if (ret) { + dev_err(dev, "dsp suspend fail\n"); + return ret; + } + } + + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int fsl_dsp_resume(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret = 0; + + if (dsp_priv->dsp_is_lpa) + return ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + if (proxy->is_ready) { + ret = xf_cmd_send_resume(proxy); + if (ret) { + dev_err(dev, "dsp resume fail\n"); + return ret; + } + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_dsp_pm = { + SET_RUNTIME_PM_OPS(fsl_dsp_runtime_suspend, + fsl_dsp_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_dsp_suspend, fsl_dsp_resume) +}; + +static const struct of_device_id fsl_dsp_ids[] = { + { .compatible = "fsl,imx8qxp-dsp-v1", }, + { .compatible = "fsl,imx8qm-dsp-v1", }, + { .compatible = "fsl,imx8mp-dsp-v1", }, + { .compatible = "fsl,imx8mp-dsp-lpa", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_dsp_ids); + +static struct platform_driver fsl_dsp_driver = { + .probe = fsl_dsp_probe, + .remove = fsl_dsp_remove, + .driver = { + .name = "fsl-dsp", + .of_match_table = fsl_dsp_ids, + .pm = &fsl_dsp_pm, + }, +}; +module_platform_driver(fsl_dsp_driver); + +MODULE_DESCRIPTION("Freescale DSP driver"); +MODULE_ALIAS("platform:fsl-dsp"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/fsl/fsl_dsp.h b/sound/soc/fsl/fsl_dsp.h new file mode 100644 index 000000000000..e56293096d9d --- /dev/null +++ b/sound/soc/fsl/fsl_dsp.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018-2020 NXP + * + */ + +#ifndef FSL_DSP_H +#define FSL_DSP_H +#include <uapi/linux/mxc_dsp.h> +#include <linux/firmware/imx/ipc.h> +#include "fsl_dsp_proxy.h" +#include "fsl_dsp_platform.h" +#include "fsl_dsp_audiomix.h" + + +#define FSL_DSP_COMP_NAME "fsl-dsp-component" + +typedef void (*memcpy_func) (void *dest, const void *src, size_t n); +typedef void (*memset_func) (void *s, int c, size_t n); + +/* ...maximal number of IPC clients per proxy */ +#define XF_CFG_MAX_IPC_CLIENTS (1 << 4) + +enum { + DSP_IMX8QXP_TYPE = 0, + DSP_IMX8QM_TYPE, + DSP_IMX8MP_TYPE, +}; + +/* ...proxy client data */ +struct xf_client { + /* ...pointer to proxy interface */ + struct xf_proxy *proxy; + + /* ...allocated proxy client id */ + u32 id; + + /* ...pending response queue */ + struct xf_msg_queue queue; + /* ...response waiting queue */ + wait_queue_head_t wait; + + /* ...virtual memory mapping */ + unsigned long vm_start; + /* ...counter of memory mappings (no real use of it yet - tbd) */ + atomic_t vm_use; + + /* ...global structure pointer */ + void *global; + struct xf_message m; + + struct snd_compr_stream *cstream; + + struct work_struct work; + struct completion compr_complete; + + int input_bytes; + int consume_bytes; + int offset; + atomic_t buffer_cnt; + int ping_pong_offset; +}; + +union xf_client_link { + /* ...index of next client in free list */ + u32 next; + + /* ...reference to proxy data for allocated client */ + struct xf_client *client; +}; + +struct fsl_dsp { + struct device *dev; + const char *fw_name; + const char *audio_iface; + void __iomem *regs; + void __iomem *mu_base_virtaddr; + struct imx_sc_ipc *dsp_ipcHandle; + struct imx_audiomix_dsp_data *audiomix; + unsigned int dsp_mu_id; + int dsp_mu_init; + int dsp_is_lpa; + atomic_long_t refcnt; + unsigned long paddr; + unsigned long dram0; + unsigned long dram1; + unsigned long iram; + unsigned long sram; + void *sdram_vir_addr; + unsigned long sdram_phys_addr; + int sdram_reserved_size; + void *dram_reserved_vir_addr; + unsigned long dram_reserved_phys_addr; + int dram_reserved_size; + void *ocram_vir_addr; + unsigned long ocram_phys_addr; + int ocram_reserved_size; + void *msg_buf_virt; + dma_addr_t msg_buf_phys; + int msg_buf_size; + void *scratch_buf_virt; + dma_addr_t scratch_buf_phys; + int scratch_buf_size; + void *dsp_config_virt; + dma_addr_t dsp_config_phys; + int dsp_config_size; + int dsp_board_type; + unsigned int fixup_offset; + + /* ...proxy data structures */ + struct xf_proxy proxy; + + /* ...mutex lock */ + struct mutex dsp_mutex; + + struct dsp_data dsp_data; + + /* ...global clients pool (item[0] serves as list terminator) */ + union xf_client_link xf_client_map[XF_CFG_MAX_IPC_CLIENTS]; + + struct clk *esai_ipg_clk; + struct clk *esai_mclk; + struct clk *asrc_mem_clk; + struct clk *asrc_ipg_clk; + struct clk *asrck_clk[4]; + struct clk *dsp_ocrama_clk; + struct clk *dsp_root_clk; + struct clk *audio_root_clk; + struct clk *audio_axi_clk; + struct clk *debug_clk; + struct clk *mu2_clk; + struct clk *sdma_root_clk; + struct clk *sai_ipg_clk; + struct clk *sai_mclk; + struct clk *uart_ipg_clk; + struct clk *uart_per_clk; + + struct device **pd_dev; + struct device_link **pd_dev_link; + int num_domains; +}; + +#define IRAM_OFFSET 0x10000 +#define IRAM_SIZE 2048 + +#define DRAM0_OFFSET 0x0 +#define DRAM0_SIZE 0x8000 + +#define DRAM1_OFFSET 0x8000 +#define DRAM1_SIZE 0x8000 + +#define SYSRAM_OFFSET 0x18000 +#define SYSRAM_SIZE 0x40000 + +#define SYSROM_OFFSET 0x58000 +#define SYSROM_SIZE 0x30000 + +#define MSG_BUF_SIZE 8192 +#define INPUT_BUF_SIZE 4096 +#define OUTPUT_BUF_SIZE 16384 +#define DSP_CONFIG_SIZE 8192 + +void *memcpy_dsp(void *dest, const void *src, size_t count); +void *memset_dsp(void *dest, int c, size_t count); +struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id); +struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv); + +int fsl_dsp_open_func(struct fsl_dsp *dsp_priv, struct xf_client *client); +int fsl_dsp_close_func(struct xf_client *client); + +#endif diff --git a/sound/soc/fsl/fsl_dsp_audiomix.c b/sound/soc/fsl/fsl_dsp_audiomix.c new file mode 100644 index 000000000000..c5022ac273f5 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_audiomix.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP. + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> + +#include "fsl_dsp_audiomix.h" + +struct imx_audiomix_dsp_data { + void __iomem *base; +}; + +void imx_audiomix_dsp_start(struct imx_audiomix_dsp_data *data) +{ + u32 val; + + val = readl(data->base + AudioDSP_REG2); + val &= ~AudioDSP_REG2_RUNSTALL; + writel(val, data->base + AudioDSP_REG2); +} +EXPORT_SYMBOL(imx_audiomix_dsp_start); + +void imx_audiomix_dsp_stall(struct imx_audiomix_dsp_data *data) +{ + u32 val; + + val = readl(data->base + AudioDSP_REG2); + val |= AudioDSP_REG2_RUNSTALL; + writel(val, data->base + AudioDSP_REG2); +} +EXPORT_SYMBOL(imx_audiomix_dsp_stall); + +void imx_audiomix_dsp_pid_set(struct imx_audiomix_dsp_data *data, u32 val) +{ + writel(val, data->base + AudioDSP_REG3); +} +EXPORT_SYMBOL(imx_audiomix_dsp_pid_set); + +bool imx_audiomix_dsp_reset(struct imx_audiomix_dsp_data *data) +{ + u32 val; + + val = readl(data->base + AudioDSP_REG3); + if (val == 0) + return true; + else + return false; +} +EXPORT_SYMBOL(imx_audiomix_dsp_reset); + +bool imx_audiomix_dsp_pwaitmode(struct imx_audiomix_dsp_data *data) +{ + u32 val; + + val = readl(data->base + AudioDSP_REG2); + if (val & AudioDSP_REG2_PWAITMODE) + return true; + else + return false; +} +EXPORT_SYMBOL(imx_audiomix_dsp_pwaitmode); + +static int imx_audiomix_dsp_probe(struct platform_device *pdev) +{ + struct imx_audiomix_dsp_data *drvdata; + struct device *dev = &pdev->dev; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (drvdata == NULL) + return -ENOMEM; + + drvdata->base = dev_get_drvdata(dev->parent); + + platform_set_drvdata(pdev, drvdata); + + return 0; +} + +static const struct of_device_id imx_audiomix_dsp_dt_ids[] = { + { .compatible = "fsl,audiomix-dsp", }, + { /* sentinel */ }, +}; + +static struct platform_driver imx_audiomix_dsp_driver = { + .probe = imx_audiomix_dsp_probe, + .driver = { + .name = "audiomix-dsp", + .of_match_table = imx_audiomix_dsp_dt_ids, + }, +}; +module_platform_driver(imx_audiomix_dsp_driver); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_dsp_audiomix.h b/sound/soc/fsl/fsl_dsp_audiomix.h new file mode 100644 index 000000000000..2920442b8a4e --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_audiomix.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + * + */ + +#ifndef FSL_DSP_AUDMIX_H +#define FSL_DSP_AUDMIX_H + +#define AudioDSP_REG0 0x100 +#define AudioDSP_REG1 0x104 +#define AudioDSP_REG2 0x108 +#define AudioDSP_REG3 0x10c + +#define AudioDSP_REG2_RUNSTALL BIT(5) +#define AudioDSP_REG2_PWAITMODE BIT(1) + +struct imx_audiomix_dsp_data; +void imx_audiomix_dsp_start(struct imx_audiomix_dsp_data *data); +void imx_audiomix_dsp_stall(struct imx_audiomix_dsp_data *data); +void imx_audiomix_dsp_pid_set(struct imx_audiomix_dsp_data *data, u32 val); +bool imx_audiomix_dsp_reset(struct imx_audiomix_dsp_data *data); +bool imx_audiomix_dsp_pwaitmode(struct imx_audiomix_dsp_data *data); + +#endif diff --git a/sound/soc/fsl/fsl_dsp_cpu.c b/sound/soc/fsl/fsl_dsp_cpu.c new file mode 100644 index 000000000000..f202cdc4ed72 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_cpu.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// DSP Audio platform driver +// +// Copyright 2018 NXP + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/core.h> +#include <sound/compress_driver.h> + +#include "fsl_dsp_cpu.h" + +static int dsp_audio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) { + return 0; +} + + +static void dsp_audio_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) { +} + +static const struct snd_soc_dai_ops dsp_audio_dai_ops = { + .startup = dsp_audio_startup, + .shutdown = dsp_audio_shutdown, +}; + +static struct snd_soc_dai_driver dsp_audio_dai = { + .name = "dsp-audio-cpu-dai", + .compress_new = snd_soc_new_compress, + .ops = &dsp_audio_dai_ops, + .playback = { + .stream_name = "Compress Playback", + .channels_min = 1, + }, +}; + +static const struct snd_soc_component_driver audio_dsp_component = { + .name = "audio-dsp", +}; + +static int dsp_audio_probe(struct platform_device *pdev) +{ + struct fsl_dsp_audio *dsp_audio; + int ret; + + dsp_audio = devm_kzalloc(&pdev->dev, sizeof(*dsp_audio), GFP_KERNEL); + if (dsp_audio == NULL) + return -ENOMEM; + + dev_dbg(&pdev->dev, "probing DSP device....\n"); + + /* intialise sof device */ + dev_set_drvdata(&pdev->dev, dsp_audio); + + pm_runtime_enable(&pdev->dev); + + /* now register audio DSP platform driver */ + ret = snd_soc_register_component(&pdev->dev, &audio_dsp_component, + &dsp_audio_dai, 1); + if (ret < 0) { + dev_err(&pdev->dev, + "error: failed to register DSP DAI driver %d\n", ret); + goto err; + } + + return 0; + +err: + return ret; +} + +static int dsp_audio_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + return 0; +} + + +static const struct of_device_id dsp_audio_ids[] = { + { .compatible = "fsl,dsp-audio"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dsp_audio_ids); + +static struct platform_driver dsp_audio_driver = { + .driver = { + .name = "dsp-audio", + .of_match_table = dsp_audio_ids, + }, + .probe = dsp_audio_probe, + .remove = dsp_audio_remove, +}; +module_platform_driver(dsp_audio_driver); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_dsp_cpu.h b/sound/soc/fsl/fsl_dsp_cpu.h new file mode 100644 index 000000000000..dac10b4ab9d0 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_cpu.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * DSP Audio DAI header + * + * Copyright 2018 NXP + */ + +#ifndef __FSL_DSP_CPU_H +#define __FSL_DSP_CPU_H + +struct fsl_dsp_audio { + struct platform_device *pdev; +}; + +#endif /*__FSL_DSP_CPU_H*/ + diff --git a/sound/soc/fsl/fsl_dsp_library_load.c b/sound/soc/fsl/fsl_dsp_library_load.c new file mode 100644 index 000000000000..d273f7296e29 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_library_load.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/elf.h> + +#include "fsl_dsp.h" +#include "fsl_dsp_library_load.h" + +static Elf32_Half xtlib_host_half(Elf32_Half v, int byteswap) +{ + return (byteswap) ? (v >> 8) | (v << 8) : v; +} + +static Elf32_Word xtlib_host_word(Elf32_Word v, int byteswap) +{ + if (byteswap) { + v = ((v & 0x00FF00FF) << 8) | ((v & 0xFF00FF00) >> 8); + v = (v >> 16) | (v << 16); + } + return v; +} + +static int xtlib_verify_magic(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Byte magic_no; + + magic_no = header->e_ident[EI_MAG0]; + if (magic_no != 0x7f) + return -1; + + magic_no = header->e_ident[EI_MAG1]; + if (magic_no != 'E') + return -1; + + magic_no = header->e_ident[EI_MAG2]; + if (magic_no != 'L') + return -1; + + magic_no = header->e_ident[EI_MAG3]; + if (magic_no != 'F') + return -1; + + if (header->e_ident[EI_CLASS] != ELFCLASS32) + return -1; + + { + /* determine byte order */ + union { + short s; + char c[sizeof(short)]; + } u; + + u.s = 1; + + if (header->e_ident[EI_DATA] == ELFDATA2LSB) + xtlib_globals->byteswap = u.c[sizeof(short) - 1] == 1; + else if (header->e_ident[EI_DATA] == ELFDATA2MSB) + xtlib_globals->byteswap = u.c[0] == 1; + else + return -1; + } + + return 0; +} + +static void xtlib_load_seg(Elf32_Phdr *pheader, void *src_addr, xt_ptr dst_addr, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Word bytes_to_copy = xtlib_host_word(pheader->p_filesz, + xtlib_globals->byteswap); + Elf32_Word bytes_to_zero = xtlib_host_word(pheader->p_memsz, + xtlib_globals->byteswap) + - bytes_to_copy; + unsigned int i; + char *src_back, *dst_back; + + void *zero_addr = (void *)dst_addr + bytes_to_copy; + + if (bytes_to_copy > 0) { + // memcpy((void *)(dst_addr), src_addr, bytes_to_copy); + src_back = (char *)src_addr; + dst_back = (char *)dst_addr; + for (i = 0; i < bytes_to_copy; i++) + *dst_back++ = *src_back++; + } + + if (bytes_to_zero > 0) { + // memset(zero_addr, 0, bytes_to_zero); + dst_back = (char *)zero_addr; + for (i = 0; i < bytes_to_zero; i++) + *dst_back++ = 0; + } +} + +#define xtlib_xt_half xtlib_host_half +#define xtlib_xt_word xtlib_host_word + +static xt_ptr align_ptr(xt_ptr ptr, xt_uint align) +{ + return (xt_ptr)(((xt_uint)ptr + align - 1) & ~(align - 1)); +} + +static xt_ptr xt_ptr_offs(xt_ptr base, Elf32_Word offs, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + + return (xt_ptr)xtlib_xt_word((xt_uint)base + + xtlib_host_word(offs, xtlib_globals->byteswap), + xtlib_globals->byteswap); +} + +static Elf32_Dyn *find_dynamic_info(Elf32_Ehdr *eheader, + struct lib_info *lib_info) +{ + char *base_addr = (char *)eheader; + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader = (Elf32_Phdr *)(base_addr + + xtlib_host_word(eheader->e_phoff, + xtlib_globals->byteswap)); + + int seg = 0; + int num = xtlib_host_half(eheader->e_phnum, xtlib_globals->byteswap); + + while (seg < num) { + if (xtlib_host_word(pheader[seg].p_type, + xtlib_globals->byteswap) == PT_DYNAMIC) { + return (Elf32_Dyn *)(base_addr + + xtlib_host_word(pheader[seg].p_offset, + xtlib_globals->byteswap)); + } + seg++; + } + return 0; +} + +static int find_align(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Shdr *sheader = (Elf32_Shdr *) (((char *)header) + + xtlib_host_word(header->e_shoff, xtlib_globals->byteswap)); + + int sec = 0; + int num = xtlib_host_half(header->e_shnum, xtlib_globals->byteswap); + + int align = 0; + + while (sec < num) { + if (sheader[sec].sh_type != SHT_NULL && + xtlib_host_word(sheader[sec].sh_size, + xtlib_globals->byteswap) > 0) { + int sec_align = + xtlib_host_word(sheader[sec].sh_addralign, + xtlib_globals->byteswap); + if (sec_align > align) + align = sec_align; + } + sec++; + } + + return align; +} + +static int validate_dynamic(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + + if (xtlib_verify_magic(header, lib_info) != 0) + return XTLIB_NOT_ELF; + + if (xtlib_host_half(header->e_type, + xtlib_globals->byteswap) != ET_DYN) + return XTLIB_NOT_DYNAMIC; + + return XTLIB_NO_ERR; +} + +static int validate_dynamic_splitload(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader; + int err = validate_dynamic(header, lib_info); + + if (err != XTLIB_NO_ERR) + return err; + + /* make sure it's split load pi library, expecting three headers, + * code, data and dynamic, for example: + * + *LOAD off 0x00000094 vaddr 0x00000000 paddr 0x00000000 align 2**0 + * filesz 0x00000081 memsz 0x00000081 flags r-x + *LOAD off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**0 + * filesz 0x000001ab memsz 0x000011bc flags rwx + *DYNAMIC off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**2 + * filesz 0x000000a0 memsz 0x000000a0 flags rw- + */ + + if (xtlib_host_half(header->e_phnum, xtlib_globals->byteswap) != 3) + return XTLIB_NOT_SPLITLOAD; + + pheader = (Elf32_Phdr *)((char *)header + + xtlib_host_word(header->e_phoff, xtlib_globals->byteswap)); + + /* LOAD R-X */ + if (xtlib_host_word(pheader[0].p_type, + xtlib_globals->byteswap) != PT_LOAD || + (xtlib_host_word(pheader[0].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_X)) + return XTLIB_NOT_SPLITLOAD; + + /* LOAD RWX */ + if (xtlib_host_word(pheader[1].p_type, + xtlib_globals->byteswap) != PT_LOAD || + (xtlib_host_word(pheader[1].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_W | PF_X)) + return XTLIB_NOT_SPLITLOAD; + + /* DYNAMIC RW- */ + if (xtlib_host_word(pheader[2].p_type, + xtlib_globals->byteswap) != PT_DYNAMIC || + (xtlib_host_word(pheader[2].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_W)) + return XTLIB_NOT_SPLITLOAD; + + return XTLIB_NO_ERR; +} + +static unsigned int +xtlib_split_pi_library_size(struct xtlib_packaged_library *library, + unsigned int *code_size, + unsigned int *data_size, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader; + Elf32_Ehdr *header = (Elf32_Ehdr *)library; + int align; + int err = validate_dynamic_splitload(header, lib_info); + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return err; + } + + align = find_align(header, lib_info); + + pheader = (Elf32_Phdr *)((char *)library + + xtlib_host_word(header->e_phoff, xtlib_globals->byteswap)); + + *code_size = xtlib_host_word(pheader[0].p_memsz, + xtlib_globals->byteswap) + align; + *data_size = xtlib_host_word(pheader[1].p_memsz, + xtlib_globals->byteswap) + align; + + return XTLIB_NO_ERR; +} + +static int get_dyn_info(Elf32_Ehdr *eheader, + xt_ptr dst_addr, xt_uint src_offs, + xt_ptr dst_data_addr, xt_uint src_data_offs, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + unsigned int jmprel = 0; + unsigned int pltrelsz = 0; + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Dyn *dyn_entry = find_dynamic_info(eheader, lib_info); + + if (dyn_entry == 0) + return XTLIB_NO_DYNAMIC_SEGMENT; + + info->dst_addr = (xt_uint)xtlib_xt_word((Elf32_Word)dst_addr, + xtlib_globals->byteswap); + info->src_offs = xtlib_xt_word(src_offs, xtlib_globals->byteswap); + info->dst_data_addr = (xt_uint)xtlib_xt_word( + (Elf32_Word)dst_data_addr + src_data_offs, + xtlib_globals->byteswap); + info->src_data_offs = xtlib_xt_word(src_data_offs, + xtlib_globals->byteswap); + + dst_addr -= src_offs; + dst_data_addr = dst_data_addr + src_data_offs - src_data_offs; + + info->start_sym = xt_ptr_offs(dst_addr, eheader->e_entry, lib_info); + + info->align = xtlib_xt_word(find_align(eheader, lib_info), + xtlib_globals->byteswap); + + info->text_addr = 0; + + while (dyn_entry->d_tag != DT_NULL) { + switch ((Elf32_Sword) xtlib_host_word( + (Elf32_Word)dyn_entry->d_tag, + xtlib_globals->byteswap)) { + case DT_RELA: + info->rel = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_RELASZ: + info->rela_count = xtlib_xt_word( + xtlib_host_word(dyn_entry->d_un.d_val, + xtlib_globals->byteswap) / + sizeof(Elf32_Rela), + xtlib_globals->byteswap); + break; + case DT_INIT: + info->init = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_FINI: + info->fini = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_HASH: + info->hash = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_SYMTAB: + info->symtab = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_STRTAB: + info->strtab = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_JMPREL: + jmprel = dyn_entry->d_un.d_val; + break; + case DT_PLTRELSZ: + pltrelsz = dyn_entry->d_un.d_val; + break; + case DT_LOPROC + 2: + info->text_addr = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + + default: + /* do nothing */ + break; + } + dyn_entry++; + } + + return XTLIB_NO_ERR; +} + +static xt_ptr +xtlib_load_split_pi_library_common(struct xtlib_packaged_library *library, + xt_ptr destination_code_address, + xt_ptr destination_data_address, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Ehdr *header = (Elf32_Ehdr *)library; + Elf32_Phdr *pheader; + unsigned int align; + int err = validate_dynamic_splitload(header, lib_info); + xt_ptr destination_code_address_back; + xt_ptr destination_data_address_back; + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return 0; + } + + align = find_align(header, lib_info); + + destination_code_address_back = destination_code_address; + destination_data_address_back = destination_data_address; + + destination_code_address = align_ptr(destination_code_address, align); + destination_data_address = align_ptr(destination_data_address, align); + lib_info->code_buf_virt += (destination_code_address - + destination_code_address_back); + lib_info->data_buf_virt += (destination_data_address - + destination_data_address_back); + + pheader = (Elf32_Phdr *)((char *)library + + xtlib_host_word(header->e_phoff, + xtlib_globals->byteswap)); + + err = get_dyn_info(header, + destination_code_address, + xtlib_host_word(pheader[0].p_paddr, + xtlib_globals->byteswap), + destination_data_address, + xtlib_host_word(pheader[1].p_paddr, + xtlib_globals->byteswap), + info, + lib_info); + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return 0; + } + + /* loading code */ + xtlib_load_seg(&pheader[0], + (char *)library + xtlib_host_word(pheader[0].p_offset, + xtlib_globals->byteswap), + (xt_ptr)lib_info->code_buf_virt, + lib_info); + + if (info->text_addr == 0) + info->text_addr = + (xt_ptr)xtlib_xt_word((Elf32_Word)destination_code_address, + xtlib_globals->byteswap); + + /* loading data */ + xtlib_load_seg(&pheader[1], + (char *)library + xtlib_host_word(pheader[1].p_offset, + xtlib_globals->byteswap), + (xt_ptr)lib_info->data_buf_virt + + xtlib_host_word(pheader[1].p_paddr, + xtlib_globals->byteswap), + lib_info); + + return (xt_ptr)xtlib_host_word((Elf32_Word)info->start_sym, + xtlib_globals->byteswap); +} + +static xt_ptr +xtlib_host_load_split_pi_library(struct xtlib_packaged_library *library, + xt_ptr destination_code_address, + xt_ptr destination_data_address, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + return xtlib_load_split_pi_library_common(library, + destination_code_address, + destination_data_address, + info, + lib_info); +} + +static long +load_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy, + struct lib_info *lib_info) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy); + unsigned char *srambuf; + struct lib_dnld_info_t dpulib; + struct file *file; + struct xf_buffer *buf; + Elf32_Phdr *pheader; + Elf32_Ehdr *header; + loff_t pos = 0; + unsigned int align; + int filesize = 0; + long ret_val = 0; + + file = filp_open(lib_info->filename, O_RDONLY, 0); + if (IS_ERR(file)) + return PTR_ERR(file); + + vfs_llseek(file, 0, SEEK_END); + filesize = (int)file->f_pos; + + srambuf = kmalloc(filesize, GFP_KERNEL); + if (!srambuf) + return -ENOMEM; + + vfs_llseek(file, 0, SEEK_SET); + ret_val = kernel_read(file, srambuf, filesize, &pos); + if (ret_val < 0) + return ret_val; + filp_close(file, NULL); + + ret_val = xtlib_split_pi_library_size( + (struct xtlib_packaged_library *)(srambuf), + (unsigned int *)&dpulib.size_code, + (unsigned int *)&dpulib.size_data, + lib_info); + if (ret_val != XTLIB_NO_ERR) + return -EINVAL; + + lib_info->code_buf_size = dpulib.size_code; + lib_info->data_buf_size = dpulib.size_data; + + header = (Elf32_Ehdr *)srambuf; + pheader = (Elf32_Phdr *)((char *)srambuf + + xtlib_host_word(header->e_phoff, + lib_info->xtlib_globals.byteswap)); + + align = find_align(header, lib_info); + ret_val = xf_pool_alloc(client, proxy, 1, dpulib.size_code + align, + XF_POOL_AUX, &lib_info->code_section_pool); + if (ret_val) { + kfree(srambuf); + pr_err("Allocation failure for loading code section\n"); + return -ENOMEM; + } + + ret_val = xf_pool_alloc(client, proxy, 1, + dpulib.size_data + pheader[1].p_paddr + align, + XF_POOL_AUX, &lib_info->data_section_pool); + if (ret_val) { + kfree(srambuf); + pr_err("Allocation failure for loading data section\n"); + return -ENOMEM; + } + + buf = xf_buffer_get(lib_info->code_section_pool); + lib_info->code_buf_virt = xf_buffer_data(buf); + lib_info->code_buf_phys = ((u64)xf_buffer_data(buf) - + (u64)dsp_priv->scratch_buf_virt) + + dsp_priv->scratch_buf_phys; + lib_info->code_buf_size = dpulib.size_code + align; + xf_buffer_put(buf); + + buf = xf_buffer_get(lib_info->data_section_pool); + lib_info->data_buf_virt = xf_buffer_data(buf); + lib_info->data_buf_phys = ((u64)xf_buffer_data(buf) - + (u64)dsp_priv->scratch_buf_virt) + + dsp_priv->scratch_buf_phys; + lib_info->data_buf_size = dpulib.size_data + align + pheader[1].p_paddr; + xf_buffer_put(buf); + + dpulib.pbuf_code = (unsigned long)lib_info->code_buf_phys; + dpulib.pbuf_data = (unsigned long)lib_info->data_buf_phys; + + dpulib.ppil_inf = &lib_info->pil_info; + xtlib_host_load_split_pi_library((struct xtlib_packaged_library *)srambuf, + (xt_ptr)(dpulib.pbuf_code), + (xt_ptr)(dpulib.pbuf_data), + (struct xtlib_pil_info *)dpulib.ppil_inf, + (void *)lib_info); + kfree(srambuf); + + return ret_val; +} + +static long +unload_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy, + struct lib_info *lib_info) +{ + xf_pool_free(client, lib_info->code_section_pool); + xf_pool_free(client, lib_info->data_section_pool); + + return 0; +} + +long xf_load_lib(struct xf_client *client, + struct xf_handle *handle, struct lib_info *lib_info) +{ + void *b = xf_handle_aux(handle); + struct icm_xtlib_pil_info icm_info; + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + long ret_val; + + ret_val = load_dpu_with_library(client, proxy, lib_info); + if (ret_val) + return ret_val; + + memcpy((void *)(&icm_info.pil_info), (void *)(&lib_info->pil_info), + sizeof(struct xtlib_pil_info)); + + icm_info.lib_type = lib_info->lib_type; + + /* ...set message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), __XF_PORT_SPEC2(handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_LOAD_LIB; + msg.buffer = b; + msg.length = sizeof(struct icm_xtlib_pil_info); + msg.ret = 0; + + /* ...copy lib info */ + memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + +// xf_msg_free(proxy, rmsg); +// xf_unlock(&proxy->lock); + + return 0; +} + +long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info) +{ + void *b = xf_handle_aux(handle); + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + struct icm_xtlib_pil_info icm_info; + + memset((void *)&icm_info, 0, sizeof(struct icm_xtlib_pil_info)); + icm_info.lib_type = lib_info->lib_type; + + /* ...set message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),__XF_PORT_SPEC2(handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNLOAD_LIB; + msg.buffer = b; + msg.length = sizeof(struct icm_xtlib_pil_info); + msg.ret = 0; + + /* ...copy lib info */ + memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + +// xf_msg_free(proxy, rmsg); +// xf_unlock(&proxy->lock); + + return unload_dpu_with_library(client, proxy, lib_info); +} diff --git a/sound/soc/fsl/fsl_dsp_library_load.h b/sound/soc/fsl/fsl_dsp_library_load.h new file mode 100644 index 000000000000..8c14dda20b27 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_library_load.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#ifndef FSL_DSP_LIBRARY_LOAD_H +#define FSL_DSP_LIBRARY_LOAD_H + +#include "fsl_dsp_pool.h" + +#define Elf32_Byte unsigned char +#define xt_ptr unsigned long +#define xt_int int +#define xt_uint unsigned int +#define xt_ulong unsigned long + +struct xtlib_packaged_library; + +enum { + XTLIB_NO_ERR = 0, + XTLIB_NOT_ELF = 1, + XTLIB_NOT_DYNAMIC = 2, + XTLIB_NOT_STATIC = 3, + XTLIB_NO_DYNAMIC_SEGMENT = 4, + XTLIB_UNKNOWN_SYMBOL = 5, + XTLIB_NOT_ALIGNED = 6, + XTLIB_NOT_SPLITLOAD = 7, + XTLIB_RELOCATION_ERR = 8 +}; + +enum lib_type { + DSP_CODEC_LIB = 1, + DSP_CODEC_WRAP_LIB +}; + +struct xtlib_loader_globals { + int err; + int byteswap; +}; + +struct xtlib_pil_info { + xt_uint dst_addr; + xt_uint src_offs; + xt_uint dst_data_addr; + xt_uint src_data_offs; + xt_uint start_sym; + xt_uint text_addr; + xt_uint init; + xt_uint fini; + xt_uint rel; + xt_int rela_count; + xt_uint hash; + xt_uint symtab; + xt_uint strtab; + xt_int align; +}; + +struct icm_xtlib_pil_info { + struct xtlib_pil_info pil_info; + unsigned int lib_type; +}; + +struct lib_dnld_info_t { + unsigned long pbuf_code; + unsigned long pbuf_data; + unsigned int size_code; + unsigned int size_data; + struct xtlib_pil_info *ppil_inf; + unsigned int lib_on_dpu; /* 0: not loaded, 1: loaded. */ +}; + +struct lib_info { + struct xtlib_pil_info pil_info; + struct xtlib_loader_globals xtlib_globals; + + struct xf_pool *code_section_pool; + struct xf_pool *data_section_pool; + + void *code_buf_virt; + unsigned int code_buf_phys; + unsigned int code_buf_size; + void *data_buf_virt; + unsigned int data_buf_phys; + unsigned int data_buf_size; + + const char *filename; + unsigned int lib_type; +}; + +long xf_load_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info); +long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info); + +#endif diff --git a/sound/soc/fsl/fsl_dsp_platform.h b/sound/soc/fsl/fsl_dsp_platform.h new file mode 100644 index 000000000000..b29f34b789a5 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_platform.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. + * Copyright 2018 NXP + */ + +#ifndef _FSL_DSP_PLATFORM_H +#define _FSL_DSP_PLATFORM_H + +#include "fsl_dsp_xaf_api.h" + +struct dsp_data { + struct xf_client *client; + struct xaf_pipeline *p_pipe; + struct xaf_pipeline pipeline; + struct xaf_comp component[2]; + int codec_type; + int renderer_type; + int status; +}; + +#endif /*_FSL_DSP_PLATFORM_H*/ diff --git a/sound/soc/fsl/fsl_dsp_platform_compress.c b/sound/soc/fsl/fsl_dsp_platform_compress.c new file mode 100644 index 000000000000..85ae2587f109 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_platform_compress.c @@ -0,0 +1,746 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// DSP driver compress implementation +// +// Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. +// Copyright 2018-2020 NXP + +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/core.h> +#include <sound/compress_driver.h> + +#include "fsl_dsp.h" +#include "fsl_dsp_platform.h" +#include "fsl_dsp_xaf_api.h" + +#define NUM_CODEC 3 +#define MIN_FRAGMENT 1 +#define MAX_FRAGMENT 1 +#define MIN_FRAGMENT_SIZE (4 * 1024) +#define MAX_FRAGMENT_SIZE (4 * 1024) + +void dsp_platform_process(struct work_struct *w) +{ + struct xf_client *client = container_of(w, struct xf_client, work); + struct xf_proxy *proxy = client->proxy; + struct xf_message *rmsg; + + while (1) { + rmsg = xf_cmd_recv(proxy, &client->wait, &client->queue, 1); + + if (!proxy->is_active || IS_ERR(rmsg)) + return; + if (rmsg->opcode == XF_EMPTY_THIS_BUFFER) { + client->consume_bytes += rmsg->length; + atomic_inc(&client->buffer_cnt); + snd_compr_fragment_elapsed(client->cstream); + + if (rmsg->buffer == NULL && rmsg->length == 0) + snd_compr_drain_notify(client->cstream); + + } else { + memcpy(&client->m, rmsg, sizeof(struct xf_message)); + complete(&client->compr_complete); + } + + xf_msg_free(proxy, rmsg); + xf_unlock(&proxy->lock); + } +} + +static int dsp_platform_compr_open(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + + if (drv->client) + return -EBUSY; + drv->client = xf_client_alloc(dsp_priv); + if (IS_ERR(drv->client)) + return PTR_ERR(drv->client); + + fsl_dsp_open_func(dsp_priv, drv->client); + + drv->client->proxy = &dsp_priv->proxy; + + cpu_dai->driver->ops->startup(NULL, cpu_dai); + + drv->client->cstream = cstream; + + INIT_WORK(&drv->client->work, dsp_platform_process); + + return 0; +} + +static int dsp_platform_compr_free(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xf_proxy *p_proxy = &dsp_priv->proxy; + int ret; + + if (cstream->runtime->state != SNDRV_PCM_STATE_PAUSED && + cstream->runtime->state != SNDRV_PCM_STATE_DRAINING && + cstream->runtime->state > SNDRV_PCM_STATE_OPEN) { + if (dsp_priv->dsp_is_lpa) { + ret = xaf_comp_flush(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_flush(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + } + + ret = xaf_comp_delete(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to delete component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_delete(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to delete component, err = %d\n", ret); + return ret; + } + xf_pool_free(drv->client, p_proxy->aux); + } + + cpu_dai->driver->ops->shutdown(NULL, cpu_dai); + + drv->client->proxy->is_active = 0; + wake_up(&drv->client->wait); + cancel_work_sync(&drv->client->work); + + fsl_dsp_close_func(drv->client); + drv->client = NULL; + + return 0; +} + +static int dsp_platform_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + /* accroding to the params, load the library and create component*/ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xf_proxy *p_proxy = &dsp_priv->proxy; + struct xf_set_param_msg s_param; + int ret; + + switch (params->codec.id) { + case SND_AUDIOCODEC_PCM: + drv->codec_type = CODEC_PCM_DEC; + atomic_set(&drv->client->buffer_cnt, 2); + break; + case SND_AUDIOCODEC_MP3: + drv->codec_type = CODEC_MP3_DEC; + break; + case SND_AUDIOCODEC_AAC: + drv->codec_type = CODEC_AAC_DEC; + break; + default: + dev_err(component->dev, "codec not supported, id =%d\n", params->codec.id); + return -EINVAL; + } + + /* ...create auxiliary buffers pool for control commands */ + ret = xf_pool_alloc(drv->client, + p_proxy, + XA_AUX_POOL_SIZE, + XA_AUX_POOL_MSG_LENGTH, + XF_POOL_AUX, + &p_proxy->aux); + if (ret) { + dev_err(component->dev, "xf_pool_alloc failed"); + return ret; + } + + /* ...create pipeline */ + ret = xaf_pipeline_create(&drv->pipeline); + if (ret) { + dev_err(component->dev, "create pipeline error\n"); + goto err_pool_alloc; + } + + /* ...create component */ + ret = xaf_comp_create(drv->client, p_proxy, &drv->component[0], + drv->codec_type); + if (ret) { + dev_err(component->dev, + "create component failed type = %d, err = %d\n", + drv->codec_type, ret); + goto err_pool_alloc; + } + if (sysfs_streq(dsp_priv->audio_iface, "sai")) + drv->renderer_type = RENDER_SAI; + else + drv->renderer_type = RENDER_ESAI; + ret = xaf_comp_create(drv->client, p_proxy, &drv->component[1], + drv->renderer_type); + if (ret) { + dev_err(component->dev, + "create component failed, type = %d, err = %d\n", + drv->renderer_type, ret); + goto err_comp0_create; + } + + if (drv->codec_type == CODEC_AAC_DEC) { + s_param.id = XA_STREAM_TYPE; + if (params->codec.format == SND_AUDIOSTREAMFORMAT_MP4ADTS || params->codec.format == SND_AUDIOSTREAMFORMAT_MP2ADTS) + s_param.mixData.value = XA_STREAM_ADTS; + else if (params->codec.format == SND_AUDIOSTREAMFORMAT_ADIF) + s_param.mixData.value = XA_STREAM_ADIF; + else + s_param.mixData.value = XA_STREAM_RAW; + ret = xaf_comp_set_config(drv->client, &drv->component[0], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp0_create; + } + + /* ...set depth before init codec */ + s_param.id = XA_DEPTH; + s_param.mixData.value = 16; + ret = xaf_comp_set_config(drv->client, &drv->component[0], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp0_create; + } + } + + + /* ...add component into pipeline */ + ret = xaf_comp_add(&drv->pipeline, &drv->component[0]); + if (ret) { + dev_err(component->dev, + "add component failed, type = %d, err = %d\n", + drv->codec_type, ret); + goto err_comp0_create; + } + + ret = xaf_comp_add(&drv->pipeline, &drv->component[1]); + if (ret) { + dev_err(component->dev, + "add component failed, type = %d, err = %d\n", + drv->renderer_type, ret); + goto err_comp1_create; + } + + ret = xaf_connect(drv->client, + &drv->component[0], + &drv->component[1], + 1, + OUTBUF_SIZE); + if (ret) { + dev_err(component->dev, "Failed to connect component, err = %d\n", ret); + goto err_comp1_create; + } + + drv->client->input_bytes = 0; + drv->client->consume_bytes = 0; + drv->client->offset = 0; + drv->client->ping_pong_offset = 0; + + if (drv->codec_type == CODEC_PCM_DEC) { + s_param.id = XA_PCM_CONFIG_PARAM_IN_PCM_WIDTH; + if (params->codec.format == SNDRV_PCM_FORMAT_S32_LE) + s_param.mixData.value = 32; + else + s_param.mixData.value = 16; + ret = xaf_comp_set_config(drv->client, &drv->component[0], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + } + + s_param.id = XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE; + s_param.mixData.value = params->codec.sample_rate; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + + s_param.id = XA_RENDERER_CONFIG_PARAM_CHANNELS; + s_param.mixData.value = params->codec.ch_out; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + + s_param.id = XA_RENDERER_CONFIG_PARAM_PCM_WIDTH; + s_param.mixData.value = 16; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + return 0; + +err_comp1_create: + xaf_comp_delete(drv->client, &drv->component[1]); +err_comp0_create: + xaf_comp_delete(drv->client, &drv->component[0]); +err_pool_alloc: + xf_pool_free(drv->client, p_proxy->aux); + + return ret; +} + +static int dsp_platform_compr_trigger_start(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int ret; + + if (!dsp_priv->dsp_is_lpa) { + ret = xaf_comp_process(drv->client, + p_comp, + p_comp->inptr, + drv->client->input_bytes, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + } + + return 0; +} + +static int dsp_platform_compr_trigger_stop(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + int ret; + + ret = xaf_comp_flush(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_flush(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + drv->client->input_bytes = 0; + drv->client->consume_bytes = 0; + drv->client->offset = 0; + drv->client->ping_pong_offset = 0; + + return 0; +} + +static int dsp_platform_compr_trigger_drain(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int ret; + + ret = xaf_comp_process(drv->client, p_comp, NULL, 0, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + return 0; +} + +static int dsp_platform_compr_trigger_pause(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret; + + ret = xf_cmd_send_pause(proxy); + if (ret) { + dev_err(component->dev, "trigger pause err = %d\n", ret); + return ret; + } + return 0; +} + +static int dsp_platform_compr_trigger_pause_release(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret; + + ret = xf_cmd_send_pause_release(proxy); + if (ret) { + dev_err(component->dev, "trigger pause release err = %d\n", ret); + return ret; + } + + return 0; +} + +static int dsp_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = dsp_platform_compr_trigger_start(cstream); + break; + case SNDRV_PCM_TRIGGER_STOP: + ret = dsp_platform_compr_trigger_stop(cstream); + break; + case SND_COMPR_TRIGGER_DRAIN: + ret = dsp_platform_compr_trigger_drain(cstream); + break; + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = dsp_platform_compr_trigger_pause(cstream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = dsp_platform_compr_trigger_pause_release(cstream); + break; + } + + /*send command*/ + return ret; +} + +static int dsp_platform_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xf_get_param_msg g_param[2]; + int ret; + + g_param[0].id = XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE; + g_param[1].id = XA_RENDERER_CONFIG_PARAM_CONSUMED; + ret = xaf_comp_get_config(drv->client, &drv->component[1], 2, &g_param); + if (ret) { + dev_err(component->dev, + "get param[cmd:0x%x|val:0x%x] error, err = %d\n", + g_param[0].id, g_param[0].mixData.value, ret); + goto out; + } + + if ((drv->codec_type != CODEC_PCM_DEC && drv->client->input_bytes != drv->client->consume_bytes) + || (drv->codec_type == CODEC_PCM_DEC && atomic_read(&drv->client->buffer_cnt) <= 0)) + tstamp->copied_total = drv->client->input_bytes+drv->client->offset-4096; + else + tstamp->copied_total = drv->client->input_bytes+drv->client->offset; + tstamp->byte_offset = drv->client->input_bytes; + tstamp->pcm_frames = 0x900; + tstamp->pcm_io_frames = g_param[1].mixData.value; + tstamp->sampling_rate = g_param[0].mixData.value; + +out: + return 0; +} + +static int dsp_platform_compr_copy(struct snd_compr_stream *cstream, + char __user *buf, + size_t count) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int copied = 0; + int ret; + + if (drv->client->input_bytes == drv->client->consume_bytes) { + if (count > INBUF_SIZE){ + ret = copy_from_user(p_comp->inptr, buf, INBUF_SIZE); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = INBUF_SIZE; + } else { + ret = copy_from_user(p_comp->inptr, buf, count); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = count; + } + drv->client->input_bytes += copied; + + if (cstream->runtime->state == SNDRV_PCM_STATE_RUNNING && copied) { + ret = xaf_comp_process(drv->client, p_comp, + p_comp->inptr, copied, + XF_EMPTY_THIS_BUFFER); + schedule_work(&drv->client->work); + } + } + + return copied; +} + +static int dsp_platform_compr_lpa_pcm_copy(struct snd_compr_stream *cstream, + char __user *buf, + size_t count) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int copied = 0; + int ret; + + if (atomic_read(&drv->client->buffer_cnt) > 0) { + if (drv->client->offset+count >= (INBUF_SIZE_LPA_PCM>>1)-4096 || !buf) { + /* buf == NULL and count == 1 is for drain and */ + /* suspend as tinycompress drain is blocking call */ + copied = count; + if (!buf) + copied = 0; + if (buf) { + ret = copy_from_user(p_comp->inptr+drv->client->ping_pong_offset+drv->client->offset, buf, copied); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + } + + if (cstream->runtime->state == SNDRV_PCM_STATE_RUNNING) { + ret = xaf_comp_process(drv->client, p_comp, + p_comp->inptr+drv->client->ping_pong_offset, drv->client->offset+copied, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + drv->client->input_bytes += drv->client->offset+copied; + drv->client->offset = 0; + atomic_dec(&drv->client->buffer_cnt); + if (drv->client->ping_pong_offset) + drv->client->ping_pong_offset = 0; + else + drv->client->ping_pong_offset = INBUF_SIZE_LPA_PCM>>1; + } + if (!buf) + copied = count; + } else { + ret = copy_from_user(p_comp->inptr+drv->client->ping_pong_offset+drv->client->offset, buf, count); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = count; + drv->client->offset += copied; + } + } + + return copied; +} + +static int dsp_platform_compr_lpa_copy(struct snd_compr_stream *cstream, + char __user *buf, + size_t count) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int copied = 0; + int ret; + + if (drv->codec_type == CODEC_PCM_DEC) + return dsp_platform_compr_lpa_pcm_copy(cstream, buf, count); + + if (drv->client->input_bytes == drv->client->consume_bytes) { + if (drv->client->offset+count >= INBUF_SIZE_LPA-4096 || !buf) { + /* buf == NULL and count == 1 is for drain and */ + /* suspend as tinycompress drain is blocking call */ + copied = count; + if (!buf) + copied = 0; + if (buf) { + ret = copy_from_user(p_comp->inptr+drv->client->offset, buf, copied); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + } + + if (cstream->runtime->state == SNDRV_PCM_STATE_RUNNING) { + ret = xaf_comp_process(drv->client, p_comp, + p_comp->inptr, drv->client->offset+copied, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + drv->client->input_bytes += drv->client->offset+copied; + drv->client->offset = 0; + } + if (!buf) + copied = count; + } else { + ret = copy_from_user(p_comp->inptr+drv->client->offset, buf, count); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = count; + drv->client->offset += copied; + } + } + + return copied; +} + +static int dsp_platform_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + caps->num_codecs = NUM_CODEC; + caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */ + caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */ + caps->min_fragments = MIN_FRAGMENT; + caps->max_fragments = MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + caps->codecs[2] = SND_AUDIOCODEC_PCM; + + return 0; +} + +static struct snd_compr_codec_caps caps_pcm = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates[0] = 192000, + .descriptor[0].sample_rates[1] = 176400, + .descriptor[0].sample_rates[2] = 96000, + .descriptor[0].sample_rates[3] = 88200, + .descriptor[0].sample_rates[4] = 48000, + .descriptor[0].sample_rates[5] = 44100, + .descriptor[0].sample_rates[6] = 32000, + .descriptor[0].sample_rates[7] = 16000, + .descriptor[0].sample_rates[8] = 8000, + .descriptor[0].num_sample_rates = 9, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 192, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = SND_AUDIOPROFILE_PCM, + .descriptor[0].modes = 0, + .descriptor[0].formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, +}; + +static struct snd_compr_codec_caps caps_mp3 = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 192, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + +static struct snd_compr_codec_caps caps_aac = { + .num_descriptors = 2, + .descriptor[1].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[1].bit_rate[0] = 320, + .descriptor[1].bit_rate[1] = 192, + .descriptor[1].num_bitrates = 2, + .descriptor[1].profiles = 0, + .descriptor[1].modes = 0, + .descriptor[1].formats = + (SND_AUDIOSTREAMFORMAT_MP4ADTS | SND_AUDIOSTREAMFORMAT_MP2ADTS | + SND_AUDIOSTREAMFORMAT_ADIF | SND_AUDIOSTREAMFORMAT_RAW), +}; + +static int dsp_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + if (codec->codec == SND_AUDIOCODEC_MP3) + *codec = caps_mp3; + else if (codec->codec == SND_AUDIOCODEC_AAC) + *codec = caps_aac; + else if (codec->codec == SND_AUDIOCODEC_PCM) + *codec = caps_pcm; + else + return -EINVAL; + + return 0; +} + +static int dsp_platform_compr_set_metadata(struct snd_compr_stream *cstream, + struct snd_compr_metadata *metadata) +{ + return 0; +} + +const struct snd_compr_ops dsp_platform_compr_ops = { + .open = dsp_platform_compr_open, + .free = dsp_platform_compr_free, + .set_params = dsp_platform_compr_set_params, + .set_metadata = dsp_platform_compr_set_metadata, + .trigger = dsp_platform_compr_trigger, + .pointer = dsp_platform_compr_pointer, + .copy = dsp_platform_compr_copy, + .get_caps = dsp_platform_compr_get_caps, + .get_codec_caps = dsp_platform_compr_get_codec_caps, +}; + +const struct snd_compr_ops dsp_platform_compr_lpa_ops = { + .open = dsp_platform_compr_open, + .free = dsp_platform_compr_free, + .set_params = dsp_platform_compr_set_params, + .set_metadata = dsp_platform_compr_set_metadata, + .trigger = dsp_platform_compr_trigger, + .pointer = dsp_platform_compr_pointer, + .copy = dsp_platform_compr_lpa_copy, + .get_caps = dsp_platform_compr_get_caps, + .get_codec_caps = dsp_platform_compr_get_codec_caps, +}; diff --git a/sound/soc/fsl/fsl_dsp_pool.c b/sound/soc/fsl/fsl_dsp_pool.c new file mode 100644 index 000000000000..637454d97231 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_pool.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Xtensa buffer pool API +// +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#include <linux/slab.h> + +#include "fsl_dsp_pool.h" +#include "fsl_dsp.h" + +/* ...allocate buffer pool */ +int xf_pool_alloc(struct xf_client *client, struct xf_proxy *proxy, + u32 number, u32 length, xf_pool_type_t type, + struct xf_pool **pool) +{ + struct xf_pool *p; + struct xf_buffer *b; + void *data; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...basic sanity checks; number of buffers is positive */ + if (number <=0) + return -EINVAL; + + /* ...get properly aligned buffer length */ + length = ALIGN(length, XF_PROXY_ALIGNMENT); + + p = kzalloc(offsetof(struct xf_pool, buffer) + + number * sizeof(struct xf_buffer), GFP_KERNEL); + if(!p) + return -ENOMEM; + + /* ...prepare command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_ALLOC; + msg.length = length * number; + msg.buffer = NULL; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) { + kfree(p); + return PTR_ERR(rmsg); + } + + p->p = rmsg->buffer; + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...if operation is failed, do cleanup */ + /* ...set pool parameters */ + p->number = number, p->length = length; + p->proxy = proxy; + + /* ...create individual buffers and link them into free list */ + for (p->free = b = &p->buffer[0], data = p->p; number > 0; + number--, b++) { + /* ...set address of the buffer (no length there) */ + b->address = data; + + /* ...file buffer into the free list */ + b->link.next = b + 1; + + /* ...advance data pointer in contiguous buffer */ + data += length; + } + + /* ...terminate list of buffers (not too good - tbd) */ + b[-1].link.next = NULL; + + /* ...return buffer pointer */ + *pool = p; + + return 0; +} +/* ...buffer pool destruction */ +int xf_pool_free(struct xf_client *client, struct xf_pool *pool) +{ + struct xf_proxy *proxy; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...basic sanity checks; pool is positive */ + if (pool == NULL) + return -EINVAL; + + /* ...get proxy pointer */ + if ((proxy = pool->proxy) == NULL) + return -EINVAL; + + /* ...prepare command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_FREE; + msg.length = pool->length * pool->number; + msg.buffer = pool->p; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + kfree(pool); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + return 0; +} + +/* ...get new buffer from a pool */ +struct xf_buffer *xf_buffer_get(struct xf_pool *pool) +{ + struct xf_buffer *b; + + xf_lock(&pool->proxy->lock); + /* ...take buffer from a head of the free list */ + b = pool->free; + if (b) { + /* ...advance free list head */ + pool->free = b->link.next, b->link.pool = pool; + } + + xf_unlock(&pool->proxy->lock); + return b; +} + +/* ...return buffer back to pool */ +void xf_buffer_put(struct xf_buffer *buffer) +{ + struct xf_pool *pool = buffer->link.pool; + + xf_lock(&pool->proxy->lock); + /* ...use global proxy lock for pool operations protection */ + /* ...put buffer back to a pool */ + buffer->link.next = pool->free, pool->free = buffer; + + xf_unlock(&pool->proxy->lock); +} diff --git a/sound/soc/fsl/fsl_dsp_pool.h b/sound/soc/fsl/fsl_dsp_pool.h new file mode 100644 index 000000000000..4a56262faf7f --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_pool.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Xtensa buffer pool API header + * + * Copyright 2018 NXP + * Copyright (c) 2012-2013 by Tensilica Inc + */ +#ifndef FSL_DSP_POOL_H +#define FSL_DSP_POOL_H + +#include <linux/types.h> +#include "fsl_dsp_proxy.h" + +/* ...buffer pool type */ +typedef u32 xf_pool_type_t; + +/* ...previous declaration of struct */ +struct xf_buffer; +struct xf_pool; +struct xf_handle; +struct xf_message; +struct xf_client; + +/* ...response callback */ +typedef void (*xf_response_cb)(struct xf_handle *h, struct xf_message *msg); + +/* ...buffer pool type */ +enum xf_pool_type { + XF_POOL_AUX = 0, + XF_POOL_INPUT = 1, + XF_POOL_OUTPUT = 2 +}; + +/* ...buffer link pointer */ +union xf_buffer_link { + /* ...pointer to next free buffer in a pool (for free buffer) */ + struct xf_buffer *next; + /* ...reference to a buffer pool (for allocated buffer) */ + struct xf_pool *pool; +}; + +/* ...buffer descriptor */ +struct xf_buffer { + /* ...virtual address of contiguous buffer */ + void *address; + /* ...link pointer */ + union xf_buffer_link link; +}; + +/* ...buffer pool */ +struct xf_pool { + /* ...reference to proxy data */ + struct xf_proxy *proxy; + /* ...length of individual buffer in a pool */ + u32 length; + /* ...number of buffers in a pool */ + u32 number; + /* ...pointer to pool memory */ + void *p; + /* ...pointer to first free buffer in a pool */ + struct xf_buffer *free; + /* ...individual buffers */ + struct xf_buffer buffer[0]; +}; + +/* component handle */ +struct xf_handle { + /* ...reference to proxy data */ + struct xf_proxy *proxy; + /* ...auxiliary control buffer for control transactions */ + struct xf_buffer *aux; + /* ...global client-id of the component */ + u32 id; + /* ...local client number (think about merging into "id" field - tbd) */ + u32 client; + /* ...response processing hook */ + xf_response_cb response; +}; + +/* ...accessor to buffer data */ +static inline void *xf_buffer_data(struct xf_buffer *buffer) +{ + return buffer->address; +} + +/* ...length of buffer data */ +static inline size_t xf_buffer_length(struct xf_buffer *buffer) +{ + struct xf_pool *pool = buffer->link.pool; + + return (size_t)pool->length; +} + +/* ...component client-id (global scope) */ +static inline u32 xf_handle_id(struct xf_handle *handle) +{ + return handle->id; +} + +/* ...pointer to auxiliary buffer */ +static inline void *xf_handle_aux(struct xf_handle *handle) +{ + return xf_buffer_data(handle->aux); +} + +int xf_pool_alloc(struct xf_client *client, struct xf_proxy *proxy, u32 number, + u32 length, xf_pool_type_t type, struct xf_pool **pool); +int xf_pool_free(struct xf_client *client, struct xf_pool *pool); + +struct xf_buffer *xf_buffer_get(struct xf_pool *pool); +void xf_buffer_put(struct xf_buffer *buffer); + +#endif /* FSL_DSP_POOL_H */ diff --git a/sound/soc/fsl/fsl_dsp_proxy.c b/sound/soc/fsl/fsl_dsp_proxy.c new file mode 100644 index 000000000000..d907b9d077dc --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_proxy.c @@ -0,0 +1,911 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +// +// DSP proxy driver transfers messages between DSP driver and DSP framework +// +// Copyright 2018 NXP +// Copyright (C) 2017 Cadence Design Systems, Inc. + +#include <linux/suspend.h> +#include "fsl_dsp_proxy.h" +#include "fsl_dsp.h" + +/* ...initialize message queue */ +void xf_msg_queue_init(struct xf_msg_queue *queue) +{ + queue->head = queue->tail = NULL; +} + +/* ...get message queue head */ +struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue) +{ + return queue->head; +} + +/* ...allocate new message from the pool */ +struct xf_message *xf_msg_alloc(struct xf_proxy *proxy) +{ + struct xf_message *m = proxy->free; + + /* ...make sure we have a free message item */ + if (m != NULL) { + /* ...get message from the pool */ + proxy->free = m->next, m->next = NULL; + } + + return m; +} + +/* ...return message to the pool of free items */ +void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m) +{ + /* ...put message into the head of free items list */ + m->next = proxy->free, proxy->free = m; + + /* ...notify potential client waiting for message */ + wake_up(&proxy->busy); +} + +/* ...return all messages from the queue to the pool of free items */ +void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue) +{ + struct xf_message *m = queue->head; + + /* ...check if there is anything in the queue */ + if (m != NULL) { + queue->tail->next = proxy->free; + proxy->free = queue->head; + queue->head = queue->tail = NULL; + + /* ...notify potential client waiting for message */ + wake_up(&proxy->busy); + } +} + +/* ...submit message to a queue */ +int xf_msg_enqueue(struct xf_msg_queue *queue, struct xf_message *m) +{ + int first = (queue->head == NULL); + + /* ...set pointer to next item */ + m->next = NULL; + + /* ...advance head/tail pointer as required */ + if (first) + queue->head = m; + else + queue->tail->next = m; + + /* ...new tail points to this message */ + queue->tail = m; + + return first; +} + +/* ...retrieve next message from the per-task queue */ +struct xf_message *xf_msg_dequeue(struct xf_msg_queue *queue) +{ + struct xf_message *m = queue->head; + + /* ...check if there is anything in the queue */ + if (m != NULL) { + /* ...pop message from the head of the list */ + queue->head = m->next; + if (queue->head == NULL) + queue->tail = NULL; + } + + return m; +} + +/* ...helper function for requesting execution message from a pool */ +struct xf_message *xf_msg_available(struct xf_proxy *proxy) +{ + struct xf_message *m; + + /* ...acquire global lock */ + xf_lock(&proxy->lock); + + /* ...try to allocate the message */ + m = xf_msg_alloc(proxy); + if (m == NULL) { + /* ...failed to allocate message; release lock */ + xf_unlock(&proxy->lock); + } + + /* ...if successfully allocated */ + return m; +} + +/* ...helper function for receiving a message from per-client queue */ +struct xf_message *xf_msg_received(struct xf_proxy *proxy, + struct xf_msg_queue *queue) +{ + struct xf_message *m; + + /* ...acquire global lock */ + xf_lock(&proxy->lock); + + /* ...try to peek message from the queue */ + m = xf_msg_dequeue(queue); + if (m == NULL) { + /* ...queue is empty; release lock */ + xf_unlock(&proxy->lock); + } + + /* ...if message is non-null, lock is held */ + return m; +} + +/* + * MU related functions + */ +u32 icm_intr_send(struct xf_proxy *proxy, u32 msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + MU_SendMessageTimeout(dsp_priv->mu_base_virtaddr, 0, msg, 800); + return 0; +} + +int icm_intr_extended_send(struct xf_proxy *proxy, + u32 msg, + struct dsp_ext_msg *ext_msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + + msghdr.allbits = msg; + if (msghdr.size != 8) + dev_err(dev, "too much ext msg\n"); + + MU_SendMessageTimeout(dsp_priv->mu_base_virtaddr, 1, ext_msg->phys, 800); + MU_SendMessageTimeout(dsp_priv->mu_base_virtaddr, 2, ext_msg->size, 800); + MU_SendMessageTimeout(dsp_priv->mu_base_virtaddr, 0, msg, 800); + + return 0; +} + +int send_dpu_ext_msg_addr(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + union icm_header_t msghdr; + struct dsp_ext_msg ext_msg; + struct dsp_mem_msg *dpu_ext_msg = + (struct dsp_mem_msg *)((unsigned char *)dsp_priv->msg_buf_virt + + (MSG_BUF_SIZE / 2)); + int ret_val = 0; + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = ICM_CORE_INIT; + msghdr.size = 8; + ext_msg.phys = dsp_priv->msg_buf_phys + (MSG_BUF_SIZE / 2); + ext_msg.size = sizeof(struct dsp_mem_msg); + + dpu_ext_msg->ext_msg_phys = dsp_priv->msg_buf_phys; + dpu_ext_msg->ext_msg_size = MSG_BUF_SIZE; + dpu_ext_msg->scratch_phys = dsp_priv->scratch_buf_phys; + dpu_ext_msg->scratch_size = dsp_priv->scratch_buf_size; + dpu_ext_msg->dsp_config_phys = dsp_priv->dsp_config_phys; + dpu_ext_msg->dsp_config_size = dsp_priv->dsp_config_size; + dpu_ext_msg->dsp_board_type = dsp_priv->dsp_board_type; + + icm_intr_extended_send(proxy, msghdr.allbits, &ext_msg); + + return ret_val; +} + +long icm_ack_wait(struct xf_proxy *proxy, u32 msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + int err; + + msghdr.allbits = msg; + /* wait response from mu */ + err = wait_for_completion_timeout(&proxy->cmd_complete, + msecs_to_jiffies(1000)); + if (!err) { + dev_err(dev, "icm ack timeout! %x\n", msg); + return -ETIMEDOUT; + } + + dev_dbg(dev, "Ack recd for message 0x%08x\n", msghdr.allbits); + + return 0; +} + +irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id) +{ + struct xf_proxy *proxy = dev_id; + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + u32 reg; + + MU_ReceiveMsg(dsp_priv->mu_base_virtaddr, 0, ®); + msghdr = (union icm_header_t)reg; + + if (dsp_priv->dsp_is_lpa) + pm_system_wakeup(); + + if (msghdr.intr == 1) { + dev_dbg(dev, "INTR: Received ICM intr, msg 0x%08x\n", + msghdr.allbits); + switch (msghdr.msg) { + case ICM_CORE_EXIT: + break; + case ICM_CORE_READY: + send_dpu_ext_msg_addr(proxy); + proxy->is_ready = 1; + complete(&proxy->cmd_complete); + break; + case XF_SUSPEND: + case XF_RESUME: + case XF_PAUSE: + case XF_PAUSE_RELEASE: + complete(&proxy->cmd_complete); + break; + default: + schedule_work(&proxy->work); + break; + } + } else if (msghdr.ack == 1) { + dev_dbg(dev, "INTR: Received ICM ack 0x%08x\n", msghdr.size); + msghdr.ack = 0; + } else { + dev_dbg(dev, "Received false ICM intr 0x%08x\n", + msghdr.allbits); + } + + return IRQ_HANDLED; +} + +/* + * Proxy related functions + */ +/* ...NULL-address specification */ +#define XF_PROXY_NULL (~0U) + +#define XF_PROXY_BADADDR (dsp_priv->scratch_buf_size) + +/* ...shared memory translation - kernel virtual address to shared address */ +u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + if (b == NULL) + return XF_PROXY_NULL; + else if ((u32)(b - dsp_priv->scratch_buf_virt) < + dsp_priv->scratch_buf_size) + return (u32)(b - dsp_priv->scratch_buf_virt); + else if (dsp_priv->dsp_is_lpa && ((u32)(b - dsp_priv->dram_reserved_vir_addr) < + dsp_priv->dram_reserved_size)) + return (u32)(b - dsp_priv->dram_reserved_vir_addr + dsp_priv->scratch_buf_size); + else + return XF_PROXY_BADADDR; +} + +/* ...shared memory translation - shared address to kernel virtual address */ +void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + if (address < dsp_priv->scratch_buf_size) + return dsp_priv->scratch_buf_virt + address; + else if (dsp_priv->dsp_is_lpa && (address < dsp_priv->scratch_buf_size + dsp_priv->dram_reserved_size)) + return dsp_priv->dram_reserved_vir_addr + address - dsp_priv->scratch_buf_size; + else if (address == XF_PROXY_NULL) + return NULL; + else + return (void *) -1; +} + +/* ...process association between response received and intended client */ +static void xf_cmap(struct xf_proxy *proxy, struct xf_message *m) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + u32 id = XF_AP_IPC_CLIENT(m->id); + struct xf_client *client; + + /* ...process messages addressed to proxy itself */ + if (id == 0) { + /* ...place message into local response queue */ + xf_msg_enqueue(&proxy->response, m); + wake_up(&proxy->wait); + return; + } + + /* ...make sure the client ID is sane */ + client = xf_client_lookup(dsp_priv, id); + if (!client) { + pr_err("rsp[id:%08x]: client lookup failed", m->id); + xf_msg_free(proxy, m); + return; + } + + /* ...make sure client is bound to this proxy interface */ + if (client->proxy != proxy) { + pr_err("rsp[id:%08x]: wrong proxy interface", m->id); + xf_msg_free(proxy, m); + return; + } + + /* ...place message into local response queue */ + if (xf_msg_enqueue(&client->queue, m)) + wake_up(&client->wait); +} + +/* ...retrieve pending responses from shared memory ring-buffer */ +static u32 xf_shmem_process_responses(struct xf_proxy *proxy) +{ + struct xf_message *m; + u32 read_idx, write_idx; + int status; + + status = 0; + + /* ...get current values of read/write pointers in response queue */ + read_idx = XF_PROXY_READ(proxy, rsp_read_idx); + write_idx = XF_PROXY_READ(proxy, rsp_write_idx); + + /* ...process all committed responses */ + while (!XF_QUEUE_EMPTY(read_idx, write_idx)) { + struct xf_proxy_message *response; + + /* ...allocate execution message */ + m = xf_msg_alloc(proxy); + if (m == NULL) + break; + + /* ...mark the interface status has changed */ + status |= (XF_QUEUE_FULL(read_idx, write_idx) ? 0x3 : 0x1); + + /* ...get oldest not yet processed response */ + response = XF_PROXY_RESPONSE(proxy, XF_QUEUE_IDX(read_idx)); + + /* ...fill message parameters */ + m->id = response->session_id; + m->opcode = response->opcode; + m->length = response->length; + m->buffer = xf_proxy_a2b(proxy, response->address); + m->ret = response->ret; + + /* ...advance local reading index copy */ + read_idx = XF_QUEUE_ADVANCE_IDX(read_idx); + + /* ...update shadow copy of reading index */ + XF_PROXY_WRITE(proxy, rsp_read_idx, read_idx); + + /* ...submit message to proper client */ + xf_cmap(proxy, m); + } + + return status; +} + +/* ...put pending commands into shared memory ring-buffer */ +static u32 xf_shmem_process_commands(struct xf_proxy *proxy) +{ + struct xf_message *m; + u32 read_idx, write_idx; + int status = 0; + + /* ...get current value of peer read pointer */ + write_idx = XF_PROXY_READ(proxy, cmd_write_idx); + read_idx = XF_PROXY_READ(proxy, cmd_read_idx); + + /* ...submit any pending commands */ + while (!XF_QUEUE_FULL(read_idx, write_idx)) { + struct xf_proxy_message *command; + + /* ...check if we have a pending command */ + m = xf_msg_dequeue(&proxy->command); + if (m == NULL) + break; + + /* ...always mark the interface status has changed */ + status |= 0x3; + + /* ...select the place for the command */ + command = XF_PROXY_COMMAND(proxy, XF_QUEUE_IDX(write_idx)); + + /* ...put the response message fields */ + command->session_id = m->id; + command->opcode = m->opcode; + command->length = m->length; + command->address = xf_proxy_b2a(proxy, m->buffer); + command->ret = m->ret; + + /* ...return message back to the pool */ + xf_msg_free(proxy, m); + + /* ...advance local writing index copy */ + write_idx = XF_QUEUE_ADVANCE_IDX(write_idx); + + /* ...update shared copy of queue write pointer */ + XF_PROXY_WRITE(proxy, cmd_write_idx, write_idx); + } + + if (status) + icm_intr_send(proxy, 0); + + return status; +} + +/* ...shared memory interface maintenance routine */ +void xf_proxy_process(struct work_struct *w) +{ + struct xf_proxy *proxy = container_of(w, struct xf_proxy, work); + int status = 0; + + /* ...get exclusive access to internal data */ + xf_lock(&proxy->lock); + + do { + /* ...process outgoing commands first */ + status = xf_shmem_process_commands(proxy); + + /* ...process all pending responses */ + status |= xf_shmem_process_responses(proxy); + + } while (status); + + /* ...unlock internal proxy data */ + xf_unlock(&proxy->lock); +} + +/* ...initialize shared memory interface */ +int xf_proxy_init(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct xf_message *m; + int i; + + /* ...create a list of all messages in a pool; set head pointer */ + proxy->free = &proxy->pool[0]; + + /* ...put all messages into a single-linked list */ + for (i = 0, m = proxy->free; i < XF_CFG_MESSAGE_POOL_SIZE - 1; i++, m++) + m->next = m + 1; + + /* ...set list tail pointer */ + m->next = NULL; + + /* ...initialize proxy lock */ + xf_lock_init(&proxy->lock); + + /* ...initialize proxy thread message queues */ + xf_msg_queue_init(&proxy->command); + xf_msg_queue_init(&proxy->response); + + /* ...initialize global busy queue */ + init_waitqueue_head(&proxy->busy); + init_waitqueue_head(&proxy->wait); + + /* ...create work structure */ + INIT_WORK(&proxy->work, xf_proxy_process); + + /* ...set pointer to shared memory */ + proxy->ipc.shmem = (struct xf_shmem_data *)dsp_priv->msg_buf_virt; + + /* ...initialize shared memory interface */ + XF_PROXY_WRITE(proxy, cmd_read_idx, 0); + XF_PROXY_WRITE(proxy, cmd_write_idx, 0); + XF_PROXY_WRITE(proxy, cmd_invalid, 0); + XF_PROXY_WRITE(proxy, rsp_read_idx, 0); + XF_PROXY_WRITE(proxy, rsp_write_idx, 0); + XF_PROXY_WRITE(proxy, rsp_invalid, 0); + + return 0; +} + +/* ...trigger shared memory interface processing */ +void xf_proxy_notify(struct xf_proxy *proxy) +{ + schedule_work(&proxy->work); +} + +/* ...submit a command to proxy pending queue (lock released upon return) */ +void xf_proxy_command(struct xf_proxy *proxy, struct xf_message *m) +{ + int first; + + /* ...submit message to proxy thread */ + first = xf_msg_enqueue(&proxy->command, m); + + /* ...release the lock */ + xf_unlock(&proxy->lock); + + /* ...notify thread about command reception */ + (first ? xf_proxy_notify(proxy), 1 : 0); +} + +/* + * Proxy cmd send and receive functions + */ +int xf_cmd_send(struct xf_proxy *proxy, + u32 id, + u32 opcode, + void *buffer, + u32 length) +{ + struct xf_message *m; + int ret; + + /* ...retrieve message handle (take the lock on success) */ + ret = wait_event_interruptible(proxy->busy, + (m = xf_msg_available(proxy)) != NULL); + if (ret) + return -EINTR; + + /* ...fill-in message parameters (lock is taken) */ + m->id = id; + m->opcode = opcode; + m->length = length; + m->buffer = buffer; + m->ret = 0; + + /* ...submit command to the proxy */ + xf_proxy_command(proxy, m); + + return 0; +} + +struct xf_message *xf_cmd_recv(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, + int wait) +{ + struct xf_message *m = NULL; + int ret; + + /* ...wait for message reception (take lock on success) */ + ret = wait_event_interruptible(*wq, + (m = xf_msg_received(proxy, queue)) != NULL || !wait + || !proxy->is_active); + if (ret) + return ERR_PTR(-EINTR); + + /* ...return message with a lock taken */ + return m; +} + +struct xf_message *xf_cmd_recv_timeout(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, int wait) +{ + struct xf_message *m; + int ret; + + /* ...wait for message reception (take lock on success) */ + ret = wait_event_interruptible_timeout(*wq, + (m = xf_msg_received(proxy, queue)) != NULL || !wait, + msecs_to_jiffies(1000)); + if (ret < 0) + return ERR_PTR(-EINTR); + + if (ret == 0) + return ERR_PTR(-ETIMEDOUT); + + /* ...return message with a lock taken */ + return m; +} + +/* ...helper function for synchronous command execution */ +struct xf_message *xf_cmd_send_recv(struct xf_proxy *proxy, + u32 id, u32 opcode, + void *buffer, + u32 length) +{ + int ret; + + /* ...send command to remote proxy */ + ret = xf_cmd_send(proxy, id, opcode, buffer, length); + if (ret) + return ERR_PTR(ret); + + /* ...wait for message delivery */ + return xf_cmd_recv(proxy, &proxy->wait, &proxy->response, 1); +} + +struct xf_message *xf_cmd_send_recv_wq(struct xf_proxy *proxy, u32 id, + u32 opcode, void *buffer, u32 length, + wait_queue_head_t *wq, + struct xf_msg_queue *queue) +{ + int ret; + + /* ...send command to remote proxy */ + ret = xf_cmd_send(proxy, id, opcode, buffer, length); + if (ret) + return ERR_PTR(ret); + + /* ...wait for message delivery */ + return xf_cmd_recv(proxy, wq, queue, 1); +} + +struct xf_message *xf_cmd_send_recv_complete(struct xf_client *client, + struct xf_proxy *proxy, + u32 id, u32 opcode, void *buffer, + u32 length, + struct work_struct *work, + struct completion *completion) +{ + struct xf_message *m; + int ret; + + /* ...retrieve message handle (take the lock on success) */ + m = xf_msg_available(proxy); + if (!m) + return ERR_PTR(-EBUSY); + + /* ...fill-in message parameters (lock is taken) */ + m->id = id; + m->opcode = opcode; + m->length = length; + m->buffer = buffer; + m->ret = 0; + + init_completion(completion); + + /* ...submit command to the proxy */ + xf_proxy_command(proxy, m); + + schedule_work(work); + + /* ...wait for message reception (take lock on success) */ + ret = wait_for_completion_timeout(completion, + msecs_to_jiffies(1000)); + if (!ret) + return ERR_PTR(-ETIMEDOUT); + + m = &client->m; + + /* ...return message with a lock taken */ + return m; +} +/* + * Proxy allocate and free memory functions + */ +/* ...allocate memory buffer for kernel use */ +int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length) +{ + struct xf_message *m; + u32 id = 0; + int ret; + + /* ...send command to remote proxy */ + m = xf_cmd_send_recv(proxy, id, XF_ALLOC, NULL, length); + if (IS_ERR(m)) { + xf_unlock(&proxy->lock); + ret = PTR_ERR(m); + return ret; + } + + /* ...check if response is expected */ + if (m->opcode == XF_ALLOC && m->buffer != NULL) { + *buffer = m->buffer; + ret = 0; + } else { + ret = -ENOMEM; + } + + /* ...free message and release proxy lock */ + xf_msg_free(proxy, m); + xf_unlock(&proxy->lock); + + return ret; +} + +/* ...free memory buffer */ +int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length) +{ + struct xf_message *m; + u32 id = 0; + int ret; + + /* ...synchronously execute freeing command */ + m = xf_cmd_send_recv(proxy, id, XF_FREE, buffer, length); + if (IS_ERR(m)) { + xf_unlock(&proxy->lock); + ret = PTR_ERR(m); + return ret; + } + + /* ...check if response is expected */ + if (m->opcode == XF_FREE) + ret = 0; + else + ret = -EINVAL; + + /* ...free message and release proxy lock */ + xf_msg_free(proxy, m); + xf_unlock(&proxy->lock); + + return ret; +} + +/* + * suspend & resume functions + */ +int xf_cmd_send_suspend(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_SUSPEND; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +int xf_cmd_send_resume(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_RESUME; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} +/* + * pause & pause_release functions + */ +int xf_cmd_send_pause(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_PAUSE; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +int xf_cmd_send_pause_release(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_PAUSE_RELEASE; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +/* ...open component handle */ +int xf_open(struct xf_client *client, struct xf_proxy *proxy, + struct xf_handle *handle, const char *id, u32 core, + xf_response_cb response) +{ + void *b; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...retrieve auxiliary control buffer from proxy - need I */ + handle->aux = xf_buffer_get(proxy->aux); + + b = xf_handle_aux(handle); + + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_REGISTER; + msg.buffer = b; + msg.length = strlen(id) + 1; + msg.ret = 0; + + /* ...copy component identifier */ + memcpy(b, (void *)id, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if (IS_ERR(rmsg)) { + xf_buffer_put(handle->aux), handle->aux = NULL; + return PTR_ERR(rmsg); + } + /* ...save received component global client-id */ + handle->id = XF_MSG_SRC(rmsg->id); + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...if failed, release buffer handle */ + /* ...operation completed successfully; assign handle data */ + handle->response = response; + handle->proxy = proxy; + + return 0; +} + +/* ...close component handle */ +int xf_close(struct xf_client *client, struct xf_handle *handle) +{ + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...do I need to take component lock here? guess no - tbd */ + + /* ...buffers and stuff? - tbd */ + + /* ...acquire global proxy lock */ + /* ...unregister component from remote DSP proxy (ignore result code) */ + + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), handle->id); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNREGISTER; + msg.buffer = NULL; + msg.length = 0; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if (IS_ERR(rmsg)) { + xf_buffer_put(handle->aux), handle->aux = NULL; + return PTR_ERR(rmsg); + } + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...wipe out proxy pointer */ + handle->proxy = NULL; + + return 0; +} diff --git a/sound/soc/fsl/fsl_dsp_proxy.h b/sound/soc/fsl/fsl_dsp_proxy.h new file mode 100644 index 000000000000..021befba6242 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_proxy.h @@ -0,0 +1,522 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * DSP proxy header - commands/responses from DSP driver to DSP ramework + * + * Copyright 2018 NXP + * Copyright (c) 2017 Cadence Design Systems, Inc. + */ + +#ifndef __FSL_DSP_PROXY_H +#define __FSL_DSP_PROXY_H + +#include <linux/wait.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/spinlock.h> +#include <linux/compiler.h> +#include <linux/dma-mapping.h> +#include <linux/platform_data/dma-imx.h> +#include <linux/mx8_mu.h> +#include <linux/interrupt.h> + +#include "fsl_dsp_pool.h" +#define XF_CFG_MESSAGE_POOL_SIZE 256 + +struct xf_client; + +/******************************************************************************* + * Local proxy data + ******************************************************************************/ + +struct xf_message; +struct xf_handle; +typedef void (*xf_response_cb)(struct xf_handle *h, struct xf_message *msg); + +/* ...execution message */ +struct xf_message { + /* ...pointer to next message in a list */ + struct xf_message *next; + + /* ...session-id */ + u32 id; + + /* ...operation code */ + u32 opcode; + + /* ...length of data buffer */ + u32 length; + + /* ...translated data pointer */ + void *buffer; + + /* ...return message status */ + u32 ret; +}; + +/* ...message queue */ +struct xf_msg_queue { + /* ...pointer to list head */ + struct xf_message *head; + + /* ...pointer to list tail */ + struct xf_message *tail; +}; + +struct xf_proxy_message { + /* ...session ID */ + u32 session_id; + + /* ...proxy API command/response code */ + u32 opcode; + + /* ...length of attached buffer */ + u32 length; + + /* ...physical address of message buffer */ + u32 address; + + /* ...return message status */ + u32 ret; +}; +/**********************************************************************/ + +enum icm_action_t { + ICM_CORE_READY = 1, + ICM_CORE_INIT, + ICM_CORE_EXIT, +}; + +/* ...adjust IPC client of message going from user-space */ +#define XF_MSG_AP_FROM_USER(id, client) (((id) & ~(0xF << 2)) | (client << 2)) + + +#define __XF_PORT_SPEC(core, id, port) ((core) | ((id) << 2) | ((port) << 8)) +#define __XF_PORT_SPEC2(id, port) ((id) | ((port) << 8)) + + +/* ...wipe out IPC client from message going to user-space */ +#define XF_MSG_AP_TO_USER(id) ((id) & ~(0xF << 18)) +#define __XF_AP_PROXY(core) ((core) | 0x8000) +#define __XF_DSP_PROXY(core) ((core) | 0x8000) + +/* ...message id contains source and destination ports specification */ +#define __XF_MSG_ID(src, dst) (((src) & 0xFFFF) | (((dst) & 0xFFFF) << 16)) +#define XF_MSG_SRC(id) (((id) >> 0) & 0xFFFF) +#define XF_MSG_SRC_CORE(id) (((id) >> 0) & 0x3) +#define XF_MSG_SRC_CLIENT(id) (((id) >> 2) & 0x3F) +#define XF_MSG_DST_CLIENT(id) (((id) >> 18) & 0x3F) + +/* ...special treatment of AP-proxy destination field */ +#define XF_AP_IPC_CLIENT(id) (((id) >> 18) & 0xF) +#define XF_AP_CLIENT(id) (((id) >> 22) & 0x1FF) +#define __XF_AP_PROXY(core) ((core) | 0x8000) +#define __XF_DSP_PROXY(core) ((core) | 0x8000) +#define __XF_AP_CLIENT(core, client) ((core) | ((client) << 6) | 0x8000) + +/* ...opcode composition with command/response data tags */ +#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F)) + +/* ...shared buffer allocation */ +#define XF_ALLOC __XF_OPCODE(0, 0, 4) + +/* ...shared buffer freeing */ +#define XF_FREE __XF_OPCODE(0, 0, 5) + +/* ...resume component operation */ +#define XF_RESUME __XF_OPCODE(0, 0, 14) + +/* ...resume component operation */ +#define XF_SUSPEND __XF_OPCODE(0, 0, 15) + +/******************************************************************************* + * Ring buffer support + ******************************************************************************/ +/* ...cache-line size on DSP */ +#define XF_PROXY_ALIGNMENT 64 + +/* ...total length of shared memory queue (for commands and responses) */ +#define XF_PROXY_MESSAGE_QUEUE_LENGTH (1 << 6) + +/* ...index mask */ +#define XF_PROXY_MESSAGE_QUEUE_MASK 0x3F + +/* ...ring-buffer index */ +#define __XF_QUEUE_IDX(idx, counter) \ + (((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) | ((counter) << 16)) + +/* ...retrieve ring-buffer index */ +#define XF_QUEUE_IDX(idx) \ + ((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) + +/* ...increment ring-buffer index */ +#define XF_QUEUE_ADVANCE_IDX(idx) \ + (((idx) + 0x10001) & (0xFFFF0000 | XF_PROXY_MESSAGE_QUEUE_MASK)) + +/* ...test if ring buffer is empty */ +#define XF_QUEUE_EMPTY(read, write) \ + ((read) == (write)) + +/* ...test if ring buffer is full */ +#define XF_QUEUE_FULL(read, write) \ + ((write) == (read) + (XF_PROXY_MESSAGE_QUEUE_LENGTH << 16)) + +/* ...basic cache operations */ +#define XF_PROXY_INVALIDATE(addr, len) { } + +#define XF_PROXY_FLUSH(addr, len) { } + +/* ...data managed by host CPU (remote) - in case of shunt it is a IPC layer */ +struct xf_proxy_host_data { + /* ...command queue */ + struct xf_proxy_message command[XF_PROXY_MESSAGE_QUEUE_LENGTH]; + + /* ...writing index into command queue */ + u32 cmd_write_idx; + + /* ...reading index for response queue */ + u32 rsp_read_idx; + + /* ...indicate command queue is valid or not */ + u32 cmd_invalid; +}; + +/* ...data managed by DSP (local) */ +struct xf_proxy_dsp_data { + /* ...response queue */ + struct xf_proxy_message response[XF_PROXY_MESSAGE_QUEUE_LENGTH]; + + /* ...writing index into response queue */ + u32 rsp_write_idx; + + /* ...reading index for command queue */ + u32 cmd_read_idx; + + /* ...indicate response queue is valid or not */ + u32 rsp_invalid; +}; + +/* ...shared memory data */ +struct xf_shmem_data { + /* ...ingoing data (maintained by DSP (local side)) */ + struct xf_proxy_host_data local; + + /* ...outgoing data (maintained by host CPU (remote side)) */ + struct xf_proxy_dsp_data remote; + +}; + +/* ...shared memory data accessor */ +#define XF_SHMEM_DATA(proxy) \ + ((proxy)->ipc.shmem) + +/* ...atomic reading */ +#define __XF_PROXY_READ_ATOMIC(var) \ + ({ XF_PROXY_INVALIDATE(&(var), sizeof(var)); \ + *(u32 *)&(var); }) + +/* ...atomic writing */ +#define __XF_PROXY_WRITE_ATOMIC(var, value) \ + ({*(u32 *)&(var) = (value); \ + XF_PROXY_FLUSH(&(var), sizeof(var)); \ + (value); }) + +/* ...accessors */ +#define XF_PROXY_READ(proxy, field) \ + __XF_PROXY_READ_##field(XF_SHMEM_DATA(proxy)) + +#define XF_PROXY_WRITE(proxy, field, v) \ + __XF_PROXY_WRITE_##field(XF_SHMEM_DATA(proxy), (v)) + +/* ...individual fields reading */ +#define __XF_PROXY_READ_cmd_write_idx(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->local.cmd_write_idx) + +#define __XF_PROXY_READ_cmd_read_idx(shmem) \ + shmem->remote.cmd_read_idx + +#define __XF_PROXY_READ_cmd_invalid(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->local.cmd_invalid) + +#define __XF_PROXY_READ_rsp_write_idx(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_write_idx) + +#define __XF_PROXY_READ_rsp_read_idx(shmem) \ + shmem->local.rsp_read_idx + +#define __XF_PROXY_READ_rsp_invalid(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_invalid) + +/* ...individual fields writings */ +#define __XF_PROXY_WRITE_cmd_write_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_write_idx, v) + +#define __XF_PROXY_WRITE_cmd_read_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.cmd_read_idx, v) + +#define __XF_PROXY_WRITE_cmd_invalid(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_invalid, v) + +#define __XF_PROXY_WRITE_rsp_read_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.rsp_read_idx, v) + +#define __XF_PROXY_WRITE_rsp_write_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_write_idx, v) + +#define __XF_PROXY_WRITE_rsp_invalid(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_invalid, v) + +/* ...command buffer accessor */ +#define XF_PROXY_COMMAND(proxy, idx) \ + (&XF_SHMEM_DATA(proxy)->local.command[(idx)]) + +/* ...response buffer accessor */ +#define XF_PROXY_RESPONSE(proxy, idx) \ + (&XF_SHMEM_DATA(proxy)->remote.response[(idx)]) + +/******************************************************************************* + * Local proxy data + ******************************************************************************/ + +struct xf_proxy_ipc_data { + /* ...shared memory data pointer */ + struct xf_shmem_data __iomem *shmem; + + /* ...core identifier */ + u32 core; + + /* ...IPC registers memory */ + void __iomem *regs; +}; + +/* ...proxy data */ +struct xf_proxy { + /* ...IPC layer data */ + struct xf_proxy_ipc_data ipc; + + /* ...shared memory status change processing item */ + struct work_struct work; + + struct completion cmd_complete; + int is_ready; + int is_active; + + /* ...internal lock */ + spinlock_t lock; + + /* ...busy queue (for clients waiting ON NOTIFIcation) */ + wait_queue_head_t busy; + + /* ...waiting queue for synchronous proxy operations */ + wait_queue_head_t wait; + + /* ...submitted commands queue */ + struct xf_msg_queue command; + + /* ...pending responses queue */ + struct xf_msg_queue response; + + /* ...global message pool */ + struct xf_message pool[XF_CFG_MESSAGE_POOL_SIZE]; + + /* ...pointer to first free message in the pool */ + struct xf_message *free; + + /* ...auxiliary buffer pool for clients */ + struct xf_pool *aux; +}; + +union icm_header_t { + struct { + u32 msg:6; + u32 sub_msg:6; // sub_msg will have ICM_MSG + u32 rsvd:3; /* reserved */ + u32 intr:1; /* intr = 1 when sending msg. */ + u32 size:15; /* =size in bytes (excluding header) */ + u32 ack:1; /* response message when ack=1 */ + }; + u32 allbits; +}; + +struct dsp_ext_msg { + u32 phys; + u32 size; +}; + +struct dsp_mem_msg { + u32 ext_msg_phys; + u32 ext_msg_size; + u32 scratch_phys; + u32 scratch_size; + u32 dsp_config_phys; + u32 dsp_config_size; + u32 dsp_board_type; +}; + +static inline void xf_lock_init(spinlock_t *lock) +{ + spin_lock_init(lock); +} + +static inline void xf_lock(spinlock_t *lock) +{ + spin_lock(lock); +} + +static inline void xf_unlock(spinlock_t *lock) +{ + spin_unlock(lock); +} + +/* ...init proxy */ +int xf_proxy_init(struct xf_proxy *proxy); + +/* ...send message to proxy */ +int xf_cmd_send(struct xf_proxy *proxy, + u32 id, + u32 opcode, + void *buffer, + u32 length); + +/* ...get message from proxy */ +struct xf_message *xf_cmd_recv(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, + int wait); + +struct xf_message* +xf_cmd_recv_timeout(struct xf_proxy *proxy, wait_queue_head_t *wq, + struct xf_msg_queue *queue, int wait); + +struct xf_message* +xf_cmd_send_recv(struct xf_proxy *proxy, u32 id, u32 opcode, + void *buffer, u32 length); + +struct xf_message* +xf_cmd_send_recv_wq(struct xf_proxy *proxy, u32 id, u32 opcode, void *buffer, + u32 length, wait_queue_head_t *wq, + struct xf_msg_queue *queue); + +struct xf_message* +xf_cmd_send_recv_complete(struct xf_client *client, struct xf_proxy *proxy, + u32 id, u32 opcode, void *buffer, u32 length, + struct work_struct *work, + struct completion *completion); + +/* ...mu interrupt handle */ +irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id); + +/* ...initialize client pending message queue */ +void xf_msg_queue_init(struct xf_msg_queue *queue); + +/* ...return current queue state */ +struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue); + +/* ...return the message back to a pool */ +void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m); + +/* ...release all pending messages */ +void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue); + +/* ...wait mu interrupt */ +long icm_ack_wait(struct xf_proxy *proxy, u32 msg); + +/* ...shared memory translation - kernel virtual address to shared address */ +u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b); + +/* ...shared memory translation - shared address to kernel virtual address */ +void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address); + +int xf_cmd_send_suspend(struct xf_proxy *proxy); +int xf_cmd_send_resume(struct xf_proxy *proxy); +int xf_cmd_send_pause(struct xf_proxy *proxy); +int xf_cmd_send_pause_release(struct xf_proxy *proxy); + +int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length); +int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length); + +int xf_open(struct xf_client *client, struct xf_proxy *proxy, + struct xf_handle *handle, const char *id, u32 core, + xf_response_cb response); + +int xf_close(struct xf_client *client, struct xf_handle *handle); + + + +/******************************************************************************* + * Opcode composition + ******************************************************************************/ + +/* ...opcode composition with command/response data tags */ +#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F)) + +/* ...accessors */ +#define XF_OPCODE_CDATA(opcode) ((opcode) & (1 << 31)) +#define XF_OPCODE_RDATA(opcode) ((opcode) & (1 << 30)) +#define XF_OPCODE_TYPE(opcode) ((opcode) & (0x3F)) + +/******************************************************************************* + * Opcode types + ******************************************************************************/ + +/* ...unregister client */ +#define XF_UNREGISTER __XF_OPCODE(0, 0, 0) + +/* ...register client at proxy */ +#define XF_REGISTER __XF_OPCODE(1, 0, 1) + +/* ...port routing command */ +#define XF_ROUTE __XF_OPCODE(1, 0, 2) + +/* ...port unrouting command */ +#define XF_UNROUTE __XF_OPCODE(1, 0, 3) + +/* ...shared buffer allocation */ +#define XF_ALLOC __XF_OPCODE(0, 0, 4) + +/* ...shared buffer freeing */ +#define XF_FREE __XF_OPCODE(0, 0, 5) + +/* ...set component parameters */ +#define XF_SET_PARAM __XF_OPCODE(1, 0, 6) + +/* ...get component parameters */ +#define XF_GET_PARAM __XF_OPCODE(1, 1, 7) + +/* ...input buffer reception */ +#define XF_EMPTY_THIS_BUFFER __XF_OPCODE(1, 0, 8) + +/* ...output buffer reception */ +#define XF_FILL_THIS_BUFFER __XF_OPCODE(0, 1, 9) + +/* ...flush specific port */ +#define XF_FLUSH __XF_OPCODE(0, 0, 10) + +/* ...start component operation */ +#define XF_START __XF_OPCODE(0, 0, 11) + +/* ...stop component operation */ +#define XF_STOP __XF_OPCODE(0, 0, 12) + +/* ...pause component operation */ +#define XF_PAUSE __XF_OPCODE(0, 0, 13) + +/* ...resume component operation */ +#define XF_RESUME __XF_OPCODE(0, 0, 14) + +/* ...resume component operation */ +#define XF_SUSPEND __XF_OPCODE(0, 0, 15) + +/* ...load lib for component operation */ +#define XF_LOAD_LIB __XF_OPCODE(0, 0, 16) + +/* ...unload lib for component operation */ +#define XF_UNLOAD_LIB __XF_OPCODE(0, 0, 17) + +/* ...component output eos operation */ +#define XF_OUTPUT_EOS __XF_OPCODE(0, 0, 18) + +#define XF_PAUSE_RELEASE __XF_OPCODE(0, 0, 19) +/* ...total amount of supported decoder commands */ +#define __XF_OP_NUM 20 + +#endif diff --git a/sound/soc/fsl/fsl_dsp_xaf_api.c b/sound/soc/fsl/fsl_dsp_xaf_api.c new file mode 100644 index 000000000000..9819ad374ed5 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_xaf_api.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +// +// Xtensa Audio Framework API for communication with DSP +// +// Copyright (C) 2017 Cadence Design Systems, Inc. +// Copyright 2018 NXP + +#include "fsl_dsp.h" +#include "fsl_dsp_xaf_api.h" + +/* ...send a command message to component */ +int xf_command(struct xf_client *client, struct xf_handle *handle, + u32 port, u32 opcode, void *buffer, u32 length) +{ + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + + /* ...fill-in message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(handle->id, port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = opcode; + msg.length = length; + msg.buffer = buffer; + msg.ret = 0; + + /* ...execute command synchronously */ + return xf_cmd_send(proxy, msg.id, msg.opcode, msg.buffer, msg.length); +} + +int xaf_comp_set_config(struct xf_client *client, struct xaf_comp *p_comp, + u32 num_param, void *p_param) +{ + struct xf_handle *p_handle; + struct xf_message msg; + struct xf_message *rmsg; + struct xf_set_param_msg *smsg; + struct xf_set_param_msg *param = (struct xf_set_param_msg *)p_param; + struct xf_proxy *proxy; + u32 i; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + /* ...set persistent stream characteristics */ + smsg = xf_buffer_data(p_handle->aux); + + for (i = 0; i < num_param; i++) { + smsg[i].id = param[i].id; + smsg[i].mixData.value = param[i].mixData.value; + } + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_SET_PARAM; + msg.length = sizeof(*smsg) * num_param; + msg.buffer = smsg; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + /* ...save received component global client-id */ + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); + */ + + /* ...make sure response is expected */ + if ((rmsg->opcode != XF_SET_PARAM) || (rmsg->buffer != smsg)) { + return -EPIPE; + } + + return 0; +} + +int xaf_comp_get_config(struct xf_client *client, struct xaf_comp *p_comp, + u32 num_param, void *p_param) +{ + + struct xf_handle *p_handle; + struct xf_message msg; + struct xf_message *rmsg; + struct xf_get_param_msg *smsg; + struct xf_get_param_msg *param = (struct xf_get_param_msg *)p_param; + struct xf_proxy *proxy; + u32 i; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + /* ...set persistent stream characteristics */ + smsg = xf_buffer_data(p_handle->aux); + + for (i = 0; i < num_param; i++) + smsg[i].id = param[i].id; + + + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_GET_PARAM; + msg.length = sizeof(*smsg) * num_param; + msg.buffer = smsg; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + /* ...save received component global client-id */ + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...make sure response is expected */ + if ((rmsg->opcode != (u32)XF_GET_PARAM) || (rmsg->buffer != smsg)) { + return -EPIPE; + } + + for (i = 0; i < num_param; i++) + param[i].mixData.value = smsg[i].mixData.value; + + return 0; +} + +int xaf_comp_flush(struct xf_client *client, struct xaf_comp *p_comp) +{ + + struct xf_handle *p_handle; + struct xf_proxy *proxy; + struct xf_message msg; + struct xf_message *rmsg; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_FLUSH; + msg.length = 0; + msg.buffer = NULL; + msg.ret = 0; + + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* ...make sure response is expected */ + if ((rmsg->opcode != (u32)XF_FLUSH) || rmsg->buffer) { + return -EPIPE; + } + + return 0; +} + +int xaf_comp_create(struct xf_client *client, struct xf_proxy *proxy, + struct xaf_comp *p_comp, int comp_type) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy); + char lib_path[200]; + char lib_wrap_path[200]; + struct xf_handle *p_handle; + struct xf_buffer *buf; + int ret = 0, size; + bool loadlib = true; + bool request_inbuf = true; + + memset((void *)p_comp, 0, sizeof(struct xaf_comp)); + + strcpy(lib_path, "/usr/lib/imx-mm/audio-codec/dsp/"); + strcpy(lib_wrap_path, "/usr/lib/imx-mm/audio-codec/dsp/"); + + p_handle = &p_comp->handle; + + p_comp->comp_type = comp_type; + + /* No need to load library for PCM */ + if (comp_type == RENDER_ESAI || comp_type == RENDER_SAI || comp_type == CODEC_PCM_DEC) + loadlib = false; + + /* Need to allocate in buffer for PCM */ + if (comp_type == RENDER_ESAI || comp_type == RENDER_SAI) + request_inbuf = false; + + if (loadlib) { + p_comp->codec_lib.filename = lib_path; + p_comp->codec_wrap_lib.filename = lib_wrap_path; + p_comp->codec_lib.lib_type = DSP_CODEC_LIB; + } + + size = INBUF_SIZE; + switch (comp_type) { + case CODEC_PCM_DEC: + p_comp->dec_id = "audio-decoder/pcm"; + if (dsp_priv->dsp_is_lpa) + size = INBUF_SIZE_LPA_PCM; + break; + case CODEC_MP3_DEC: + p_comp->dec_id = "audio-decoder/mp3"; + strcat(lib_path, "lib_dsp_mp3_dec.so"); + if (dsp_priv->dsp_is_lpa) + size = INBUF_SIZE_LPA; + break; + case CODEC_AAC_DEC: + p_comp->dec_id = "audio-decoder/aac"; + strcat(lib_path, "lib_dsp_aac_dec.so"); + break; + case RENDER_ESAI: + p_comp->dec_id = "renderer/esai"; + break; + case RENDER_SAI: + p_comp->dec_id = "renderer/sai"; + break; + + default: + return -EINVAL; + break; + } + + /* ...create decoder component instance (select core-0) */ + ret = xf_open(client, proxy, p_handle, p_comp->dec_id, 0, NULL); + if (ret) { + dev_err(dsp_priv->dev, "create (%s) component error: %d\n", + p_comp->dec_id, ret); + return ret; + } + + if (loadlib) { + strcat(lib_wrap_path, "lib_dsp_codec_wrap.so"); + p_comp->codec_wrap_lib.lib_type = DSP_CODEC_WRAP_LIB; + + /* ...load codec wrapper lib */ + ret = xf_load_lib(client, p_handle, &p_comp->codec_wrap_lib); + if (ret) { + dev_err(dsp_priv->dev, "load codec wrap lib error\n"); + goto err_wrap_load; + } + + /* ...load codec lib */ + ret = xf_load_lib(client, p_handle, &p_comp->codec_lib); + if (ret) { + dev_err(dsp_priv->dev, "load codec lib error\n"); + goto err_codec_load; + } + } + + if (request_inbuf) { + /* ...allocate input buffer */ + ret = xf_pool_alloc(client, proxy, 1, size, + XF_POOL_INPUT, &p_comp->inpool); + if (ret) { + dev_err(dsp_priv->dev, "alloc input buf error\n"); + goto err_pool_alloc; + } + + /* ...initialize input buffer pointer */ + buf = xf_buffer_get(p_comp->inpool); + p_comp->inptr = xf_buffer_data(buf); + } + + p_comp->active = true; + + return ret; + +err_pool_alloc: + xf_unload_lib(client, p_handle, &p_comp->codec_lib); +err_codec_load: + xf_unload_lib(client, p_handle, &p_comp->codec_wrap_lib); +err_wrap_load: + xf_close(client, p_handle); + + return ret; +} + +int xaf_comp_delete(struct xf_client *client, struct xaf_comp *p_comp) +{ + + struct xf_handle *p_handle; + bool loadlib = true; + u32 ret = 0; + + if (!p_comp->active) + return ret; + + /* mark component as unusable from this point */ + p_comp->active = false; + + if (p_comp->comp_type == RENDER_ESAI || p_comp->comp_type == RENDER_SAI) + loadlib = false; + + p_handle = &p_comp->handle; + + if (loadlib) { + /* ...unload codec wrapper library */ + xf_unload_lib(client, p_handle, &p_comp->codec_wrap_lib); + + /* ...unload codec library */ + xf_unload_lib(client, p_handle, &p_comp->codec_lib); + + xf_pool_free(client, p_comp->inpool); + } + + /* ...delete component */ + xf_close(client, p_handle); + + return ret; +} + +int xaf_comp_process(struct xf_client *client, struct xaf_comp *p_comp, void *p_buf, u32 length, u32 flag) +{ + struct xf_handle *p_handle; + u32 ret = 0; + + p_handle = &p_comp->handle; + + switch (flag) { + case XF_FILL_THIS_BUFFER: + /* ...send message to component output port (port-id=1) */ + ret = xf_command(client, p_handle, 1, XF_FILL_THIS_BUFFER, + p_buf, length); + break; + case XF_EMPTY_THIS_BUFFER: + /* ...send message to component input port (port-id=0) */ + ret = xf_command(client, p_handle, 0, XF_EMPTY_THIS_BUFFER, + p_buf, length); + break; + default: + break; + } + + return ret; +} + +/* ...port binding function */ +int xf_route(struct xf_client *client, struct xf_handle *src, u32 src_port, + struct xf_handle *dst, u32 dst_port, u32 num, u32 size, u32 align) +{ + struct xf_proxy *proxy = src->proxy; + struct xf_buffer *b; + struct xf_route_port_msg *m; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...sanity checks - proxy pointers are same */ + if (proxy != dst->proxy) + return -EINVAL; + + /* ...buffer data is sane */ + if (!(num && size && xf_is_power_of_two(align))) + return -EINVAL; + + /* ...get control buffer */ + if ((b = xf_buffer_get(proxy->aux)) == NULL) + return -EBUSY; + + /* ...get message buffer */ + m = xf_buffer_data(b); + + /* ...fill-in message parameters */ + m->src = __XF_PORT_SPEC2(src->id, src_port); + m->dst = __XF_PORT_SPEC2(dst->id, dst_port); + m->alloc_number = num; + m->alloc_size = size; + m->alloc_align = align; + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), + __XF_PORT_SPEC2(src->id, src_port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_ROUTE; + msg.length = sizeof(*m); + msg.buffer = m; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* ...save received component global client-id */ + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + + /* ...synchronously execute command on remote DSP */ + /* XF_CHK_API(xf_proxy_cmd_exec(proxy, &msg)); */ + + /* ...return buffer to proxy */ + xf_buffer_put(b); + + /* ...check result is successful */ + /* XF_CHK_ERR(msg.opcode == XF_ROUTE, -ENOMEM); */ + + return 0; +} + +/* ...port unbinding function */ +int xf_unroute(struct xf_client *client, struct xf_handle *src, u32 src_port) +{ + struct xf_proxy *proxy = src->proxy; + struct xf_buffer *b; + struct xf_unroute_port_msg *m; + struct xf_message msg; + struct xf_message *rmsg; + int r = 0; + + /* ...get control buffer */ + if((b = xf_buffer_get(proxy->aux)) == NULL) + return -EBUSY; + + /* ...get message buffer */ + m = xf_buffer_data(b); + + /* ...fill-in message parameters */ + m->src = __XF_PORT_SPEC2(src->id, src_port); + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), + __XF_PORT_SPEC2(src->id, src_port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNROUTE; + msg.length = sizeof(*m); + msg.buffer = m; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + /* ...save received component global client-id */ + + /*TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); */ + /* xf_unlock(&proxy->lock); */ + + /* ...return buffer to proxy */ + xf_buffer_put(b); + + return r; +} + +int xaf_connect(struct xf_client *client, + struct xaf_comp *p_src, + struct xaf_comp *p_dest, + u32 num_buf, + u32 buf_length) +{ + /* ...connect p_src output port with p_dest input port */ + return xf_route(client, &p_src->handle, 1, &p_dest->handle, 0, + num_buf, buf_length, 8); +} + +int xaf_disconnect(struct xf_client *client, struct xaf_comp *p_comp) +{ + /* ...disconnect p_src output port with p_dest input port */ + return xf_unroute(client, &p_comp->handle, 0); + +} + +int xaf_comp_add(struct xaf_pipeline *p_pipe, struct xaf_comp *p_comp) +{ + int ret = 0; + + p_comp->next = p_pipe->comp_chain; + p_comp->pipeline = p_pipe; + p_pipe->comp_chain = p_comp; + + return ret; +} + +int xaf_pipeline_create(struct xaf_pipeline *p_pipe) +{ + int ret = 0; + + memset(p_pipe, 0, sizeof(struct xaf_pipeline)); + + return ret; +} + +int xaf_pipeline_delete(struct xaf_pipeline *p_pipe) +{ + int ret = 0; + + memset(p_pipe, 0, sizeof(struct xaf_pipeline)); + + return ret; +} diff --git a/sound/soc/fsl/fsl_dsp_xaf_api.h b/sound/soc/fsl/fsl_dsp_xaf_api.h new file mode 100644 index 000000000000..7b2dfa48977d --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_xaf_api.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Xtensa Audio Framework API for communication with DSP + * + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + */ +#ifndef FSL_DSP_XAF_API_H +#define FSL_DSP_XAF_API_H + +#include "fsl_dsp_library_load.h" + +/* ...size of auxiliary pool for communication with DSP */ +#define XA_AUX_POOL_SIZE 32 + +/* ...length of auxiliary pool messages */ +#define XA_AUX_POOL_MSG_LENGTH 128 + +/* ...number of max input buffers */ +#define INBUF_SIZE 4096 +/* ...buffer size of the buffer shared between A core and DSP. Use large */ +/* ...to let A core suspend longer time to save power.*/ +#define INBUF_SIZE_LPA (128*1024) +/* ...ping-pong buffer locate in DRAM for PCM LPA. */ +#define INBUF_SIZE_LPA_PCM (8*1024*1024) +#define OUTBUF_SIZE 16384 + +struct xaf_pipeline; + +struct xaf_info_s { + u32 opcode; + void *buf; + u32 length; + u32 ret; +}; + +struct xaf_comp { + struct xaf_comp *next; + + struct xaf_pipeline *pipeline; + struct xf_handle handle; + + const char *dec_id; + int comp_type; + + struct xf_pool *inpool; + struct xf_pool *outpool; + void *inptr; + void *outptr; + + struct lib_info codec_lib; + struct lib_info codec_wrap_lib; + + int active; /* component fully initialized */ +}; + +struct xaf_pipeline { + struct xaf_comp *comp_chain; + + u32 input_eos; + u32 output_eos; +}; + +int xaf_comp_create(struct xf_client *client, struct xf_proxy *p_proxy, + struct xaf_comp *p_comp, int comp_type); +int xaf_comp_delete(struct xf_client *client, struct xaf_comp *p_comp); +int xaf_comp_flush(struct xf_client *client, struct xaf_comp *p_comp); + +int xaf_comp_set_config(struct xf_client *client,struct xaf_comp *p_comp, + u32 num_param, void *p_param); +int xaf_comp_get_config(struct xf_client *client,struct xaf_comp *p_comp, + u32 num_param, void *p_param); + +int xaf_comp_add(struct xaf_pipeline *p_pipe, struct xaf_comp *p_comp); +int xaf_comp_process(struct xf_client *client, struct xaf_comp *p_comp, + void *p_buf, u32 length, u32 flag); +int xaf_comp_get_status(struct xaf_comp *p_comp, struct xaf_info_s *p_info); +int xaf_comp_get_msg_count(struct xaf_comp *p_comp); + +int xaf_connect(struct xf_client *client,struct xaf_comp *p_src, + struct xaf_comp *p_dest, u32 num_buf, u32 buf_length); +int xaf_disconnect(struct xf_client *client,struct xaf_comp *p_comp); + +int xaf_pipeline_create(struct xaf_pipeline *p_pipe); +int xaf_pipeline_delete(struct xaf_pipeline *p_pipe); + +int xaf_pipeline_send_eos(struct xaf_pipeline *p_pipe); + +/* ...port routing command */ +struct __attribute__((__packed__)) xf_route_port_msg { + /* ...source port specification */ + u32 src; + /* ...destination port specification */ + u32 dst; + /* ...number of buffers to allocate */ + u32 alloc_number; + /* ...length of buffer to allocate */ + u32 alloc_size; + /* ...alignment restriction for a buffer */ + u32 alloc_align; +}; + +/* ...port unrouting command */ +struct __attribute__((__packed__)) xf_unroute_port_msg { + /* ...source port specification */ + u32 src; + /* ...destination port specification */ + u32 dst; +}; + +/* ...check if non-zero value is a power-of-two */ +#define xf_is_power_of_two(v) (((v) & ((v) - 1)) == 0) + + +/******************************************************************************* + * bascial message + ******************************************************************************/ +typedef union DATA { + u32 value; + + struct { + u32 size; + u32 channel_table[10]; + } chan_map_tab; + + struct { + u32 samplerate; + u32 width; + u32 depth; + u32 channels; + u32 endian; + u32 interleave; + u32 layout[12]; + u32 chan_pos_set; // indicate if channel position is set outside or use codec default + } outputFormat; +} data_t; + +/* ...component initialization parameter */ +struct __attribute__((__packed__)) xf_set_param_msg { + /* ...index of parameter passed to SET_CONFIG_PARAM call */ + u32 id; + /* ...value of parameter */ + data_t mixData; +}; + +/* ...message body (command/response) */ +struct __attribute__((__packed__)) xf_get_param_msg { + /* ...array of parameters requested */ + u32 id; + /* ...array of parameters values */ + data_t mixData; +}; + +/* ...renderer-specific configuration parameters */ +enum xa_config_param_renderer { + XA_RENDERER_CONFIG_PARAM_CB = 0, + XA_RENDERER_CONFIG_PARAM_STATE = 1, + XA_RENDERER_CONFIG_PARAM_PCM_WIDTH = 2, + XA_RENDERER_CONFIG_PARAM_CHANNELS = 3, + XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE = 4, + XA_RENDERER_CONFIG_PARAM_FRAME_SIZE = 5, + XA_RENDERER_CONFIG_PARAM_CONSUMED = 6, + XA_RENDERER_CONFIG_PARAM_NUM = 7, +}; + +/* pcm codec configuration parameters */ +enum xa_config_param_pcm { + XA_PCM_CONFIG_PARAM_SAMPLE_RATE = 0, /* not supported */ + XA_PCM_CONFIG_PARAM_IN_PCM_WIDTH = 1, + XA_PCM_CONFIG_PARAM_IN_CHANNELS = 2, /* not supported */ + XA_PCM_CONFIG_PARAM_OUT_PCM_WIDTH = 3, /* not supported */ + XA_PCM_CONFIG_PARAM_OUT_CHANNELS = 4, /* not supported */ + XA_PCM_CONFIG_PARAM_CHANROUTING = 5, /* not supported */ + XA_PCM_CONFIG_PARAM_FUNC_PRINT = 13, /* not supported */ + XA_PCM_CONFIG_PARAM_NUM = 14, /* not supported */ +}; + +#endif /* FSL_DSP_XAF_API_H */ diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c new file mode 100644 index 000000000000..af490e341dd3 --- /dev/null +++ b/sound/soc/fsl/fsl_easrc.c @@ -0,0 +1,2555 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/kobject.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/sched/signal.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/gcd.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/core.h> + +#include "fsl_easrc.h" +#include "imx-pcm.h" + +extern struct snd_soc_component_driver fsl_easrc_dma_component; + +#define FSL_EASRC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_FLOAT_LE) + +static int fsl_easrc_iec958_put_bits(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_easrc *easrc = snd_soc_component_get_drvdata(comp); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regval = ucontrol->value.integer.value[0]; + + easrc->bps_iec958[mc->regbase] = regval; + + return 0; +} + +static int fsl_easrc_iec958_get_bits(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_easrc *easrc = snd_soc_component_get_drvdata(comp); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + + ucontrol->value.enumerated.item[0] = easrc->bps_iec958[mc->regbase]; + + return 0; +} + +int fsl_easrc_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regval; + int ret; + + ret = snd_soc_component_read(component, mc->regbase, ®val); + if (ret < 0) + return ret; + + ucontrol->value.integer.value[0] = regval; + + return 0; +} + +int fsl_easrc_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regval = ucontrol->value.integer.value[0]; + int ret; + + ret = snd_soc_component_write(component, mc->regbase, regval); + if (ret < 0) + return ret; + + return 0; +} + +#define SOC_SINGLE_REG_RW(xname, xreg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_xr_sx, .get = fsl_easrc_get_reg, \ + .put = fsl_easrc_set_reg, \ + .private_value = (unsigned long)&(struct soc_mreg_control) \ + { .regbase = xreg, .regcount = 1, .nbits = 32, \ + .invert = 0, .min = 0, .max = 0xffffffff, } } + +#define SOC_SINGLE_VAL_RW(xname, xreg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = snd_soc_info_xr_sx, .get = fsl_easrc_iec958_get_bits, \ + .put = fsl_easrc_iec958_put_bits, \ + .private_value = (unsigned long)&(struct soc_mreg_control) \ + { .regbase = xreg, .regcount = 1, .nbits = 32, \ + .invert = 0, .min = 0, .max = 2, } } + +static const struct snd_kcontrol_new fsl_easrc_snd_controls[] = { + SOC_SINGLE("Context 0 Dither Switch", REG_EASRC_COC(0), 0, 1, 0), + SOC_SINGLE("Context 1 Dither Switch", REG_EASRC_COC(1), 0, 1, 0), + SOC_SINGLE("Context 2 Dither Switch", REG_EASRC_COC(2), 0, 1, 0), + SOC_SINGLE("Context 3 Dither Switch", REG_EASRC_COC(3), 0, 1, 0), + + SOC_SINGLE("Context 0 IEC958 Validity", REG_EASRC_COC(0), 2, 1, 0), + SOC_SINGLE("Context 1 IEC958 Validity", REG_EASRC_COC(1), 2, 1, 0), + SOC_SINGLE("Context 2 IEC958 Validity", REG_EASRC_COC(2), 2, 1, 0), + SOC_SINGLE("Context 3 IEC958 Validity", REG_EASRC_COC(3), 2, 1, 0), + + SOC_SINGLE_VAL_RW("Context 0 IEC958 Bits Per Sample", 0), + SOC_SINGLE_VAL_RW("Context 1 IEC958 Bits Per Sample", 1), + SOC_SINGLE_VAL_RW("Context 2 IEC958 Bits Per Sample", 2), + SOC_SINGLE_VAL_RW("Context 3 IEC958 Bits Per Sample", 3), + + SOC_SINGLE_REG_RW("Context 0 IEC958 CS0", REG_EASRC_CS0(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS0", REG_EASRC_CS0(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS0", REG_EASRC_CS0(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS0", REG_EASRC_CS0(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS1", REG_EASRC_CS1(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS1", REG_EASRC_CS1(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS1", REG_EASRC_CS1(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS1", REG_EASRC_CS1(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS2", REG_EASRC_CS2(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS2", REG_EASRC_CS2(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS2", REG_EASRC_CS2(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS2", REG_EASRC_CS2(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS3", REG_EASRC_CS3(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS3", REG_EASRC_CS3(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS3", REG_EASRC_CS3(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS3", REG_EASRC_CS3(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS4", REG_EASRC_CS4(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS4", REG_EASRC_CS4(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS4", REG_EASRC_CS4(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS4", REG_EASRC_CS4(3)), + SOC_SINGLE_REG_RW("Context 0 IEC958 CS5", REG_EASRC_CS5(0)), + SOC_SINGLE_REG_RW("Context 1 IEC958 CS5", REG_EASRC_CS5(1)), + SOC_SINGLE_REG_RW("Context 2 IEC958 CS5", REG_EASRC_CS5(2)), + SOC_SINGLE_REG_RW("Context 3 IEC958 CS5", REG_EASRC_CS5(3)), +}; + +/* set_rs_ratio + * + * According to the resample taps, calculate the resample ratio + */ +static int set_rs_ratio(struct fsl_easrc_context *ctx) +{ + struct fsl_easrc *easrc = ctx->easrc; + unsigned int in_rate = ctx->in_params.norm_rate; + unsigned int out_rate = ctx->out_params.norm_rate; + unsigned int int_bits; + unsigned int frac_bits; + u64 val; + u32 *r; + int ret; + + switch (easrc->rs_num_taps) { + case EASRC_RS_32_TAPS: + int_bits = 5; + frac_bits = 39; + break; + case EASRC_RS_64_TAPS: + int_bits = 6; + frac_bits = 38; + break; + case EASRC_RS_128_TAPS: + int_bits = 7; + frac_bits = 37; + break; + default: + return -EINVAL; + } + + val = ((uint64_t)in_rate << frac_bits) / out_rate; + r = (uint32_t *)&val; + + if (r[1] & 0xFFFFF000) { + dev_err(&easrc->pdev->dev, "ratio exceed range\n"); + return -EINVAL; + } + + ret = regmap_write(easrc->regmap, + REG_EASRC_RRL(ctx->index), + EASRC_RRL_RS_RL(r[0])); + ret |= regmap_write(easrc->regmap, + REG_EASRC_RRH(ctx->index), + EASRC_RRH_RS_RH(r[1])); + + return ret; +} + +/* normalize input and output sample rates */ +static void fsl_easrc_normalize_rates(struct fsl_easrc_context *ctx) +{ + int a, b; + + if (!ctx) + return; + + a = ctx->in_params.sample_rate; + b = ctx->out_params.sample_rate; + + a = gcd(a, b); + + /* divide by gcd to normalize the rate */ + ctx->in_params.norm_rate = ctx->in_params.sample_rate / a; + ctx->out_params.norm_rate = ctx->out_params.sample_rate / a; +} + +/* resets the pointer of the coeff memory pointers */ +static int fsl_coeff_mem_ptr_reset(struct fsl_easrc *easrc, + unsigned int ctx_id, int mem_type) +{ + struct device *dev; + int ret = 0; + u32 reg, mask, val; + + if (!easrc) + return -ENODEV; + + dev = &easrc->pdev->dev; + + switch (mem_type) { + case EASRC_PF_COEFF_MEM: + /* This resets the prefilter memory pointer addr */ + if (ctx_id >= EASRC_CTX_MAX_NUM) { + dev_err(dev, "Invalid context id[%d]\n", ctx_id); + return -EINVAL; + } + + reg = REG_EASRC_CCE1(ctx_id); + mask = EASRC_CCE1_COEF_MEM_RST_MASK; + val = EASRC_CCE1_COEF_MEM_RST; + break; + case EASRC_RS_COEFF_MEM: + /* This resets the resampling memory pointer addr */ + reg = REG_EASRC_CRCC; + mask = EASRC_CRCC_RS_CPR_MASK; + val = EASRC_CRCC_RS_CPR; + break; + default: + dev_err(dev, "Unknown memory type\n"); + return -EINVAL; + } + + /* To reset the write pointer back to zero, the register field + * ASRC_CTX_CTRL_EXT1x[PF_COEFF_MEM_RST] can be toggled from + * 0x0 to 0x1 to 0x0. + */ + ret |= regmap_update_bits(easrc->regmap, reg, mask, 0); + ret |= regmap_update_bits(easrc->regmap, reg, mask, val); + ret |= regmap_update_bits(easrc->regmap, reg, mask, 0); + + return ret; +} + +static inline uint32_t bits_taps_to_val(unsigned int t) +{ + switch (t) { + case EASRC_RS_32_TAPS: + return 32; + case EASRC_RS_64_TAPS: + return 64; + case EASRC_RS_128_TAPS: + return 128; + } + + return 0; +} + +static int fsl_easrc_resampler_config(struct fsl_easrc *easrc) +{ + struct device *dev = &easrc->pdev->dev; + struct asrc_firmware_hdr *hdr = easrc->firmware_hdr; + struct interp_params *interp = easrc->interp; + struct interp_params *selected_interp = NULL; + unsigned int num_coeff; + unsigned int i; + u64 *arr; + u32 r0, r1; + u32 *r; + int ret; + + if (!hdr) { + dev_err(dev, "firmware not loaded!\n"); + return -ENODEV; + } + + for (i = 0; i < hdr->interp_scen; i++) { + if ((interp[i].num_taps - 1) == + bits_taps_to_val(easrc->rs_num_taps)) { + arr = interp[i].coeff; + selected_interp = &interp[i]; + dev_dbg(dev, "Selected interp_filter: %u taps - %u phases\n", + selected_interp->num_taps, + selected_interp->num_phases); + break; + } + } + + if (!selected_interp) { + dev_err(dev, "failed to get interpreter configuration\n"); + return -EINVAL; + } + + /* + * RS_LOW - first half of center tap of the sinc function + * RS_HIGH - second half of center tap of the sinc function + * This is due to the fact the resampling function must be + * symetrical - i.e. odd number of taps + */ + r = (uint32_t *)&selected_interp->center_tap; + r0 = r[0] & EASRC_32b_MASK; + r1 = r[1] & EASRC_32b_MASK; + + ret = regmap_write(easrc->regmap, + REG_EASRC_RCTCL, + EASRC_RCTCL_RS_CL(r0)); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_RCTCH, + EASRC_RCTCH_RS_CH(r1)); + if (ret) + return ret; + + /* Write Number of Resampling Coefficient Taps + * 00b - 32-Tap Resampling Filter + * 01b - 64-Tap Resampling Filter + * 10b - 128-Tap Resampling Filter + * 11b - N/A + */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CRCC, + EASRC_CRCC_RS_TAPS_MASK, + EASRC_CRCC_RS_TAPS(easrc->rs_num_taps)); + if (ret) + return ret; + + /* Reset prefilter coefficient pointer back to 0 */ + ret = fsl_coeff_mem_ptr_reset(easrc, 0, EASRC_RS_COEFF_MEM); + if (ret) + return ret; + + /* When the filter is programmed to run in: + * 32-tap mode, 16-taps, 128-phases 4-coefficients per phase + * 64-tap mode, 32-taps, 64-phases 4-coefficients per phase + * 128-tap mode, 64-taps, 32-phases 4-coefficients per phase + * This means the number of writes is constant no matter + * the mode we are using + */ + num_coeff = 16 * 128 * 4; + + for (i = 0; i < num_coeff; i++) { + r = (uint32_t *)&arr[i]; + r0 = r[0] & EASRC_32b_MASK; + r1 = r[1] & EASRC_32b_MASK; + + ret |= regmap_write(easrc->regmap, + REG_EASRC_CRCM, + EASRC_CRCM_RS_CWD(r0)); + + ret |= regmap_write(easrc->regmap, + REG_EASRC_CRCM, + EASRC_CRCM_RS_CWD(r1)); + + if (ret) + return ret; + } + + return 0; +} + +/***************************************************************************** + * Scale filter coefficients (64 bits float) + * For input float32 normalized range (1.0,-1.0) -> output int[16,24,32]: + * scale it by multiplying filter coefficients by 2^31 + * For input int[16, 24, 32] -> output float32 + * scale it by multiplying filter coefficients by 2^-15, 2^-23, 2^-31 + * input: + * easrc: Structure pointer of fsl_easrc + * filterIn : Pointer to non-scaled input filter + * shift: The multiply factor + * output: + * filterOut: scaled filter + *****************************************************************************/ +static int NormalizedFilterForFloat32InIntOut(struct fsl_easrc *easrc, + uint64_t *filterIn, + uint64_t *filterOut, + int shift) +{ + struct device *dev = &easrc->pdev->dev; + uint64_t coef = *filterIn; + int64_t exp = (coef & 0x7ff0000000000000ll) >> 52; + uint64_t coefOut; + + /* + * If exponent is zero (value == 0), or 7ff (value == NaNs) + * dont touch the content + */ + if (((coef & 0x7ff0000000000000ll) == 0) || + ((coef & 0x7ff0000000000000ll) == ((uint64_t)0x7ff << 52))) { + *filterOut = coef; + } else { + if ((shift > 0 && (shift + exp) >= 2047) || + (shift < 0 && (exp + shift) <= 0)) { + dev_err(dev, "coef error\n"); + return -EINVAL; + } + + /* coefficient * 2^shift ==> coefficient_exp + shift */ + exp += shift; + coefOut = (uint64_t)(coef & 0x800FFFFFFFFFFFFFll) + + ((uint64_t)exp << 52); + *filterOut = coefOut; + } + + return 0; +} + +static int write_pf_coeff_mem(struct fsl_easrc *easrc, int ctx_id, + u64 *arr, int n_taps, int shift) +{ + struct device *dev = &easrc->pdev->dev; + int ret = 0; + int i; + u32 *r; + u32 r0, r1; + u64 tmp; + + /* If STx_NUM_TAPS is set to 0x0 then return */ + if (!n_taps) + return 0; + + if (!arr) { + dev_err(dev, "NULL buffer\n"); + return -EINVAL; + } + + /* When switching between stages, the address pointer + * should be reset back to 0x0 before performing a write + */ + ret = fsl_coeff_mem_ptr_reset(easrc, ctx_id, EASRC_PF_COEFF_MEM); + if (ret) + return ret; + + for (i = 0; i < (n_taps + 1) / 2; i++) { + ret = NormalizedFilterForFloat32InIntOut(easrc, + &arr[i], + &tmp, + shift); + if (ret) + return ret; + + r = (uint32_t *)&tmp; + + r0 = r[0] & EASRC_32b_MASK; + r1 = r[1] & EASRC_32b_MASK; + + ret |= regmap_write(easrc->regmap, + REG_EASRC_PCF(ctx_id), + EASRC_PCF_CD(r0)); + + ret |= regmap_write(easrc->regmap, + REG_EASRC_PCF(ctx_id), + EASRC_PCF_CD(r1)); + + if (ret) + return ret; + } + + return 0; +} + +static int fsl_easrc_prefilter_config(struct fsl_easrc *easrc, + unsigned int ctx_id) +{ + struct fsl_easrc_context *ctx; + struct asrc_firmware_hdr *hdr; + struct prefil_params *prefil, *selected_prefil = NULL; + struct device *dev; + u32 inrate, outrate, offset = 0; + int ret, i; + + /* to modify prefilter coeficients, the user must perform + * a write in ASRC_PRE_COEFF_FIFO[COEFF_DATA] while the + * RUN_EN for that context is set to 0 + */ + if (!easrc) + return -ENODEV; + + dev = &easrc->pdev->dev; + + if (ctx_id >= EASRC_CTX_MAX_NUM) { + dev_err(dev, "Invalid context id[%d]\n", ctx_id); + return -EINVAL; + } + + ctx = easrc->ctx[ctx_id]; + + ctx->in_filled_sample = bits_taps_to_val(easrc->rs_num_taps) / 2; + ctx->out_missed_sample = ctx->in_filled_sample * + ctx->out_params.sample_rate / + ctx->in_params.sample_rate; + + ctx->st1_num_taps = 0; + ctx->st2_num_taps = 0; + + ret = regmap_write(easrc->regmap, REG_EASRC_CCE1(ctx_id), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, REG_EASRC_CCE2(ctx_id), 0); + if (ret) + return ret; + + /* prefilter is enabled only when doing downsampling. + * When out_rate >= in_rate, pf will be in bypass mode + */ + if (ctx->out_params.sample_rate >= ctx->in_params.sample_rate) { + if (ctx->out_params.sample_rate == ctx->in_params.sample_rate) { + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_RS_BYPASS_MASK, + EASRC_CCE1_RS_BYPASS); + if (ret) + return ret; + } + + if (ctx->in_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE && + ctx->out_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE) { + ctx->st1_num_taps = 1; + ctx->st1_coeff = &easrc->const_coeff; + ctx->st1_num_exp = 1; + ctx->st2_num_taps = 0; + ctx->st1_addexp = 31; + } else if (ctx->in_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE && + ctx->out_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE) { + ctx->st1_num_taps = 1; + ctx->st1_coeff = &easrc->const_coeff; + ctx->st1_num_exp = 1; + ctx->st2_num_taps = 0; + ctx->st1_addexp -= ctx->in_params.fmt.addexp; + } else { + ctx->st1_num_taps = 1; + ctx->st1_coeff = &easrc->const_coeff; + ctx->st1_num_exp = 1; + ctx->st2_num_taps = 0; + } + } else { + inrate = ctx->in_params.norm_rate; + outrate = ctx->out_params.norm_rate; + + hdr = easrc->firmware_hdr; + prefil = easrc->prefil; + + for (i = 0; i < hdr->prefil_scen; i++) { + if (inrate == prefil[i].insr && outrate == prefil[i].outsr) { + selected_prefil = &prefil[i]; + dev_dbg(dev, "Selected prefilter: %u insr, %u outsr, %u st1_taps, %u st2_taps\n", + selected_prefil->insr, + selected_prefil->outsr, + selected_prefil->st1_taps, + selected_prefil->st2_taps); + break; + } + } + + if (!selected_prefil) { + dev_err(dev, "Conversion from in ratio %u(%u) to out ratio %u(%u) is not supported\n", + ctx->in_params.sample_rate, + inrate, + ctx->out_params.sample_rate, outrate); + return -EINVAL; + } + + /* in prefilter coeff array, first st1_num_taps represent the + * stage1 prefilter coefficients followed by next st2_num_taps + * representing stage 2 coefficients + */ + ctx->st1_num_taps = selected_prefil->st1_taps; + ctx->st1_coeff = selected_prefil->coeff; + ctx->st1_num_exp = selected_prefil->st1_exp; + + offset = ((selected_prefil->st1_taps + 1) / 2) * + sizeof(selected_prefil->coeff[0]); + ctx->st2_num_taps = selected_prefil->st2_taps; + ctx->st2_coeff = (uint64_t *)((uint64_t)selected_prefil->coeff + offset); + + if (ctx->in_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE && + ctx->out_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE) { + /* only change stage2 coefficient for 2 stage case */ + if (ctx->st2_num_taps > 0) + ctx->st2_addexp = 31; + else + ctx->st1_addexp = 31; + } else if (ctx->in_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE && + ctx->out_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE) { + if (ctx->st2_num_taps > 0) + ctx->st2_addexp -= ctx->in_params.fmt.addexp; + else + ctx->st1_addexp -= ctx->in_params.fmt.addexp; + } + } + + ctx->in_filled_sample += (ctx->st1_num_taps / 2) * ctx->st1_num_exp + + ctx->st2_num_taps / 2; + ctx->out_missed_sample = ctx->in_filled_sample * + ctx->out_params.sample_rate / + ctx->in_params.sample_rate; + + if (ctx->in_filled_sample * ctx->out_params.sample_rate % + ctx->in_params.sample_rate != 0) + ctx->out_missed_sample += 1; + /* To modify the value of a prefilter coefficient, the user must + * perform a write to the register ASRC_PRE_COEFF_FIFOn[COEFF_DATA] + * while the respective context RUN_EN bit is set to 0b0 + */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx_id), + EASRC_CC_EN_MASK, 0); + if (ret) + goto ctx_error; + + if (ctx->st1_num_taps > EASRC_MAX_PF_TAPS) { + dev_err(dev, "ST1 taps [%d] mus be lower than %d\n", + ctx->st1_num_taps, EASRC_MAX_PF_TAPS); + ret = -EINVAL; + goto ctx_error; + } + + /* Update ctx ST1_NUM_TAPS in Context Control Extended 2 register */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE2(ctx_id), + EASRC_CCE2_ST1_TAPS_MASK, + EASRC_CCE2_ST1_TAPS(ctx->st1_num_taps - 1)); + if (ret) + goto ctx_error; + + /* Prefilter Coefficient Write Select to write in ST1 coeff */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_COEF_WS_MASK, + (EASRC_PF_ST1_COEFF_WR << EASRC_CCE1_COEF_WS_SHIFT)); + if (ret) + goto ctx_error; + + ret = write_pf_coeff_mem(easrc, ctx_id, + ctx->st1_coeff, + ctx->st1_num_taps, + ctx->st1_addexp); + if (ret) + goto ctx_error; + + if (ctx->st2_num_taps > 0) { + if (ctx->st2_num_taps + ctx->st1_num_taps > EASRC_MAX_PF_TAPS) { + dev_err(dev, "ST2 taps [%d] mus be lower than %d\n", + ctx->st2_num_taps, EASRC_MAX_PF_TAPS); + ret = -EINVAL; + goto ctx_error; + } + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_PF_TSEN_MASK, + EASRC_CCE1_PF_TSEN); + if (ret) + goto ctx_error; + /* + * Enable prefilter stage1 writeback floating point + * which is used for FLOAT_LE case + */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_PF_ST1_WBFP_MASK, + EASRC_CCE1_PF_ST1_WBFP); + if (ret) + goto ctx_error; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_PF_EXP_MASK, + EASRC_CCE1_PF_EXP(ctx->st1_num_exp - 1)); + if (ret) + goto ctx_error; + + /* Update ctx ST2_NUM_TAPS in Context Control Extended 2 reg */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE2(ctx_id), + EASRC_CCE2_ST2_TAPS_MASK, + EASRC_CCE2_ST2_TAPS(ctx->st2_num_taps - 1)); + if (ret) + goto ctx_error; + + /* Prefilter Coefficient Write Select to write in ST2 coeff */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_COEF_WS_MASK, + EASRC_PF_ST2_COEFF_WR << EASRC_CCE1_COEF_WS_SHIFT); + if (ret) + goto ctx_error; + + ret = write_pf_coeff_mem(easrc, ctx_id, + ctx->st2_coeff, + ctx->st2_num_taps, + ctx->st2_addexp); + if (ret) + goto ctx_error; + } + + return 0; + +ctx_error: + return ret; +} + +static int fsl_easrc_max_ch_for_slot(struct fsl_easrc_context *ctx, + struct fsl_easrc_slot *slot) +{ + int st1_mem_alloc = 0, st2_mem_alloc = 0; + int pf_mem_alloc = 0; + int max_channels = 8 - slot->num_channel; + int channels = 0; + + if (ctx->st1_num_taps > 0) { + if (ctx->st2_num_taps > 0) + st1_mem_alloc = + (ctx->st1_num_taps - 1) * ctx->st1_num_exp + 1; + else + st1_mem_alloc = ctx->st1_num_taps; + } + + if (ctx->st2_num_taps > 0) + st2_mem_alloc = ctx->st2_num_taps; + + pf_mem_alloc = st1_mem_alloc + st2_mem_alloc; + + if (pf_mem_alloc != 0) + channels = (6144 - slot->pf_mem_used) / pf_mem_alloc; + else + channels = 8; + + if (channels < max_channels) + max_channels = channels; + + return max_channels; +} + +static int fsl_easrc_config_one_slot(struct fsl_easrc_context *ctx, + struct fsl_easrc_slot *slot, + unsigned int slot_idx, + unsigned int reg0, + unsigned int reg1, + unsigned int reg2, + unsigned int reg3, + unsigned int *req_channels, + unsigned int *start_channel, + unsigned int *avail_channel) +{ + struct fsl_easrc *easrc = ctx->easrc; + int st1_chanxexp, st1_mem_alloc = 0, st2_mem_alloc = 0; + unsigned int addr; + int ret; + + if (*req_channels <= *avail_channel) { + slot->num_channel = *req_channels; + slot->min_channel = *start_channel; + slot->max_channel = *start_channel + *req_channels - 1; + slot->ctx_index = ctx->index; + slot->busy = true; + *start_channel += *req_channels; + *req_channels = 0; + } else { + slot->num_channel = *avail_channel; + slot->min_channel = *start_channel; + slot->max_channel = *start_channel + *avail_channel - 1; + slot->ctx_index = ctx->index; + slot->busy = true; + *start_channel += *avail_channel; + *req_channels -= *avail_channel; + } + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_MAXCH_MASK, + EASRC_DPCS0R0_MAXCH(slot->max_channel)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_MINCH_MASK, + EASRC_DPCS0R0_MINCH(slot->min_channel)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_NUMCH_MASK, + EASRC_DPCS0R0_NUMCH(slot->num_channel - 1)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_CTXNUM_MASK, + EASRC_DPCS0R0_CTXNUM(slot->ctx_index)); + if (ret) + return ret; + + if (ctx->st1_num_taps > 0) { + if (ctx->st2_num_taps > 0) + st1_mem_alloc = + (ctx->st1_num_taps - 1) * slot->num_channel * + ctx->st1_num_exp + slot->num_channel; + else + st1_mem_alloc = ctx->st1_num_taps * slot->num_channel; + + slot->pf_mem_used = st1_mem_alloc; + ret = regmap_update_bits(easrc->regmap, + reg2, + EASRC_DPCS0R2_ST1_MA_MASK, + EASRC_DPCS0R2_ST1_MA(st1_mem_alloc)); + if (ret) + return ret; + + if (slot_idx == 1) + addr = 0x1800 - st1_mem_alloc; + else + addr = 0; + + ret = regmap_update_bits(easrc->regmap, + reg2, + EASRC_DPCS0R2_ST1_SA_MASK, + EASRC_DPCS0R2_ST1_SA(addr)); + if (ret) + return ret; + } + + if (ctx->st2_num_taps > 0) { + st1_chanxexp = slot->num_channel * (ctx->st1_num_exp - 1); + + ret = regmap_update_bits(easrc->regmap, + reg1, + EASRC_DPCS0R1_ST1_EXP_MASK, + EASRC_DPCS0R1_ST1_EXP(st1_chanxexp)); + if (ret) + return ret; + + st2_mem_alloc = slot->num_channel * ctx->st2_num_taps; + slot->pf_mem_used += st2_mem_alloc; + ret = regmap_update_bits(easrc->regmap, + reg3, + EASRC_DPCS0R3_ST2_MA_MASK, + EASRC_DPCS0R3_ST2_MA(st2_mem_alloc)); + if (ret) + return ret; + + if (slot_idx == 1) + addr = 0x1800 - st1_mem_alloc - st2_mem_alloc; + else + addr = st1_mem_alloc; + + ret = regmap_update_bits(easrc->regmap, + reg3, + EASRC_DPCS0R3_ST2_SA_MASK, + EASRC_DPCS0R3_ST2_SA(addr)); + if (ret) + return ret; + } + + ret = regmap_update_bits(easrc->regmap, + reg0, + EASRC_DPCS0R0_EN_MASK, + EASRC_DPCS0R0_EN); + if (ret) + return ret; + + return 0; +} + +/* fsl_easrc_config_slot + * + * A single context can be split amongst any of the 4 context processing pipes + * in the design. + * The total number of channels consumed within the context processor must be + * less than or equal to 8. if a single context is configured to contain more + * than 8 channels then it must be distributed across multiple context + * processing pipe slots. + * + */ +static int fsl_easrc_config_slot(struct fsl_easrc *easrc, unsigned int ctx_id) +{ + struct fsl_easrc_context *ctx = easrc->ctx[ctx_id]; + int req_channels = ctx->channels; + int start_channel = 0, avail_channel; + struct fsl_easrc_slot *slot0, *slot1; + int i, ret; + + if (req_channels <= 0) + return -EINVAL; + + for (i = 0; i < EASRC_CTX_MAX_NUM; i++) { + slot0 = &easrc->slot[i][0]; + slot1 = &easrc->slot[i][1]; + + if (slot0->busy && slot1->busy) + continue; + + if (!slot0->busy) { + if (slot1->busy && slot1->ctx_index == ctx->index) + continue; + + avail_channel = fsl_easrc_max_ch_for_slot(ctx, slot1); + if (avail_channel <= 0) + continue; + + ret = fsl_easrc_config_one_slot(ctx, + slot0, 0, + REG_EASRC_DPCS0R0(i), + REG_EASRC_DPCS0R1(i), + REG_EASRC_DPCS0R2(i), + REG_EASRC_DPCS0R3(i), + &req_channels, + &start_channel, + &avail_channel); + if (ret) + return ret; + + if (req_channels > 0) + continue; + else + break; + } + + if (slot0->busy && !slot1->busy) { + if (slot0->ctx_index == ctx->index) + continue; + + avail_channel = fsl_easrc_max_ch_for_slot(ctx, slot0); + if (avail_channel <= 0) + continue; + + ret = fsl_easrc_config_one_slot(ctx, + slot1, 1, + REG_EASRC_DPCS1R0(i), + REG_EASRC_DPCS1R1(i), + REG_EASRC_DPCS1R2(i), + REG_EASRC_DPCS1R3(i), + &req_channels, + &start_channel, + &avail_channel); + if (ret) + return ret; + + if (req_channels > 0) + continue; + else + break; + } + } + + if (req_channels > 0) { + dev_err(&easrc->pdev->dev, "no avail slot.\n"); + return -EINVAL; + } + + return 0; +} + +/* fsl_easrc_release_slot + * + * clear the slot configuration + */ +static int fsl_easrc_release_slot(struct fsl_easrc *easrc, unsigned int ctx_id) +{ + struct fsl_easrc_context *ctx = easrc->ctx[ctx_id]; + int i, ret; + + for (i = 0; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->slot[i][0].busy && + easrc->slot[i][0].ctx_index == ctx->index) { + easrc->slot[i][0].busy = false; + easrc->slot[i][0].num_channel = 0; + easrc->slot[i][0].pf_mem_used = 0; + /* set registers */ + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS0R0(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS0R1(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS0R2(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS0R3(i), 0); + if (ret) + return ret; + } + + if (easrc->slot[i][1].busy && + easrc->slot[i][1].ctx_index == ctx->index) { + easrc->slot[i][1].busy = false; + easrc->slot[i][1].num_channel = 0; + easrc->slot[i][1].pf_mem_used = 0; + /* set registers */ + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS1R0(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS1R1(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS1R2(i), 0); + if (ret) + return ret; + + ret = regmap_write(easrc->regmap, + REG_EASRC_DPCS1R3(i), 0); + if (ret) + return ret; + } + } + + return 0; +} + +/* fsl_easrc_config_context + * + * configure the register relate with context. + */ +int fsl_easrc_config_context(struct fsl_easrc *easrc, unsigned int ctx_id) +{ + struct fsl_easrc_context *ctx; + struct device *dev; + unsigned long lock_flags; + int ret; + + /* to modify prefilter coeficients, the user must perform + * a write in ASRC_PRE_COEFF_FIFO[COEFF_DATA] while the + * RUN_EN for that context is set to 0 + */ + if (!easrc) + return -ENODEV; + + dev = &easrc->pdev->dev; + + if (ctx_id >= EASRC_CTX_MAX_NUM) { + dev_err(dev, "Invalid context id[%d]\n", ctx_id); + return -EINVAL; + } + + ctx = easrc->ctx[ctx_id]; + + fsl_easrc_normalize_rates(ctx); + + ret = set_rs_ratio(ctx); + if (ret) + return ret; + + /* initialize the context coeficients */ + ret = fsl_easrc_prefilter_config(easrc, ctx->index); + if (ret) + return ret; + + spin_lock_irqsave(&easrc->lock, lock_flags); + ret = fsl_easrc_config_slot(easrc, ctx->index); + spin_unlock_irqrestore(&easrc->lock, lock_flags); + if (ret) + return ret; + + /* Both prefilter and resampling filters can use following + * initialization modes: + * 2 - zero-fil mode + * 1 - replication mode + * 0 - software control + */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_RS_INIT_MASK, + EASRC_CCE1_RS_INIT(ctx->rs_init_mode)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CCE1(ctx_id), + EASRC_CCE1_PF_INIT_MASK, + EASRC_CCE1_PF_INIT(ctx->pf_init_mode)); + if (ret) + return ret; + + /* Context Input FIFO Watermark */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx_id), + EASRC_CC_FIFO_WTMK_MASK, + EASRC_CC_FIFO_WTMK(ctx->in_params.fifo_wtmk)); + if (ret) + return ret; + + /* Context Output FIFO Watermark */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx_id), + EASRC_COC_FIFO_WTMK_MASK, + EASRC_COC_FIFO_WTMK(ctx->out_params.fifo_wtmk - 1)); + if (ret) + return ret; + + /* number of channels */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx_id), + EASRC_CC_CHEN_MASK, + EASRC_CC_CHEN(ctx->channels - 1)); + return ret; +} + +static int fsl_easrc_process_format(struct fsl_easrc_context *ctx, + struct fsl_easrc_data_fmt *fmt, + snd_pcm_format_t raw_fmt) +{ + struct fsl_easrc *easrc = ctx->easrc; + int ret; + + if (!fmt) + return -EINVAL; + + /* Context Input Floating Point Format + * 0 - Integer Format + * 1 - Single Precision FP Format + */ + fmt->floating_point = !snd_pcm_format_linear(raw_fmt); + fmt->sample_pos = 0; + fmt->iec958 = 0; + + /* get the data width */ + switch (snd_pcm_format_width(raw_fmt)) { + case 16: + fmt->width = EASRC_WIDTH_16_BIT; + fmt->addexp = 15; + break; + case 20: + fmt->width = EASRC_WIDTH_20_BIT; + fmt->addexp = 19; + break; + case 24: + fmt->width = EASRC_WIDTH_24_BIT; + fmt->addexp = 23; + break; + case 32: + fmt->width = EASRC_WIDTH_32_BIT; + fmt->addexp = 31; + break; + default: + return -EINVAL; + } + + switch (raw_fmt) { + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + fmt->width = easrc->bps_iec958[ctx->index]; + fmt->iec958 = 1; + fmt->floating_point = 0; + if (fmt->width == EASRC_WIDTH_16_BIT) { + fmt->sample_pos = 12; + fmt->addexp = 15; + } else if (fmt->width == EASRC_WIDTH_20_BIT) { + fmt->sample_pos = 8; + fmt->addexp = 19; + } else if (fmt->width == EASRC_WIDTH_24_BIT) { + fmt->sample_pos = 4; + fmt->addexp = 23; + } + break; + default: + break; + } + + /* Data Endianness + * 0 - Little-Endian + * 1 - Big-Endian + */ + ret = snd_pcm_format_big_endian(raw_fmt); + if (ret < 0) + return ret; + + fmt->endianness = ret; + /* Input Data sign + * 0b - Signed Format + * 1b - Unsigned Format + */ + fmt->unsign = snd_pcm_format_unsigned(raw_fmt) > 0 ? 1 : 0; + + return 0; +} + +int fsl_easrc_set_ctx_format(struct fsl_easrc_context *ctx, + snd_pcm_format_t *in_raw_format, + snd_pcm_format_t *out_raw_format) +{ + struct fsl_easrc *easrc = ctx->easrc; + struct fsl_easrc_data_fmt *in_fmt = &ctx->in_params.fmt; + struct fsl_easrc_data_fmt *out_fmt = &ctx->out_params.fmt; + int ret; + + /* get the bitfield values for input data format */ + if (in_raw_format && out_raw_format) { + ret = fsl_easrc_process_format(ctx, in_fmt, *in_raw_format); + if (ret) + return ret; + } + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_BPS_MASK, + EASRC_CC_BPS(in_fmt->width)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_ENDIANNESS_MASK, + in_fmt->endianness << EASRC_CC_ENDIANNESS_SHIFT); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_FMT_MASK, + in_fmt->floating_point << EASRC_CC_FMT_SHIFT); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_INSIGN_MASK, + in_fmt->unsign << EASRC_CC_INSIGN_SHIFT); + + /* In Sample Position */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_SAMPLE_POS_MASK, + EASRC_CC_SAMPLE_POS(in_fmt->sample_pos)); + if (ret) + return ret; + + /* get the bitfield values for input data format */ + if (in_raw_format && out_raw_format) { + ret = fsl_easrc_process_format(ctx, out_fmt, *out_raw_format); + if (ret) + return ret; + } + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_BPS_MASK, + EASRC_COC_BPS(out_fmt->width)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_ENDIANNESS_MASK, + out_fmt->endianness << EASRC_COC_ENDIANNESS_SHIFT); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_FMT_MASK, + out_fmt->floating_point << EASRC_COC_FMT_SHIFT); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_OUTSIGN_MASK, + out_fmt->unsign << EASRC_COC_OUTSIGN_SHIFT); + if (ret) + return ret; + + /* Out Sample Position */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_SAMPLE_POS_MASK, + EASRC_COC_SAMPLE_POS(out_fmt->sample_pos)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_IEC_EN_MASK, + out_fmt->iec958 << EASRC_COC_IEC_EN_SHIFT); + + return ret; +} + +/* The ASRC provides interleaving support in hardware to ensure that a + * variety of sample sources can be internally combined + * to conform with this format. Interleaving parameters are accessed + * through the ASRC_CTRL_IN_ACCESSa and ASRC_CTRL_OUT_ACCESSa registers + */ +int fsl_easrc_set_ctx_organziation(struct fsl_easrc_context *ctx) +{ + struct device *dev; + struct fsl_easrc *easrc; + int ret; + + if (!ctx) + return -ENODEV; + + easrc = ctx->easrc; + dev = &easrc->pdev->dev; + + /* input interleaving parameters */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CIA(ctx->index), + EASRC_CIA_ITER_MASK, + EASRC_CIA_ITER(ctx->in_params.iterations)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CIA(ctx->index), + EASRC_CIA_GRLEN_MASK, + EASRC_CIA_GRLEN(ctx->in_params.group_len)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CIA(ctx->index), + EASRC_CIA_ACCLEN_MASK, + EASRC_CIA_ACCLEN(ctx->in_params.access_len)); + if (ret) + return ret; + + /* output interleaving parameters */ + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COA(ctx->index), + EASRC_COA_ITER_MASK, + EASRC_COA_ITER(ctx->out_params.iterations)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COA(ctx->index), + EASRC_COA_GRLEN_MASK, + EASRC_COA_GRLEN(ctx->out_params.group_len)); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COA(ctx->index), + EASRC_COA_ACCLEN_MASK, + EASRC_COA_ACCLEN(ctx->out_params.access_len)); + if (ret) + return ret; + + return 0; +} + +/* Request one of the available contexts + * + * Returns a negative number on error and >=0 as context id + * on success + */ +int fsl_easrc_request_context(struct fsl_easrc_context *ctx, + unsigned int channels) +{ + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_easrc *easrc = ctx->easrc; + struct device *dev; + unsigned long lock_flags; + int ret = 0; + int i; + + dev = &easrc->pdev->dev; + + spin_lock_irqsave(&easrc->lock, lock_flags); + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->ctx[i]) + continue; + + index = i; + break; + } + + if (index == ASRC_INVALID_PAIR) { + dev_err(dev, "all contexts are busy\n"); + ret = -EBUSY; + } else if (channels > easrc->chn_avail) { + dev_err(dev, "can't give the required channels: %d\n", + channels); + ret = -EINVAL; + } else { + ctx->index = index; + ctx->channels = channels; + easrc->ctx[index] = ctx; + easrc->chn_avail -= channels; + } + + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + return ret; +} + +/* Release the context + * + * This funciton is mainly doing the revert thing in request context + */ +int fsl_easrc_release_context(struct fsl_easrc_context *ctx) +{ + unsigned long lock_flags; + struct fsl_easrc *easrc; + struct device *dev; + int ret; + + if (!ctx) + return 0; + + easrc = ctx->easrc; + dev = &easrc->pdev->dev; + + spin_lock_irqsave(&easrc->lock, lock_flags); + + ret = fsl_easrc_release_slot(easrc, ctx->index); + + easrc->chn_avail += ctx->channels; + easrc->ctx[ctx->index] = NULL; + + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + return ret; +} + +/* Start the context + * + * Enable the DMA request and context + */ +int fsl_easrc_start_context(struct fsl_easrc_context *ctx) +{ + struct fsl_easrc *easrc = ctx->easrc; + int ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_FWMDE_MASK, + EASRC_CC_FWMDE); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_FWMDE_MASK, + EASRC_COC_FWMDE); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_EN_MASK, + EASRC_CC_EN); + if (ret) + return ret; + + return 0; +} + +/* Stop the context + * + * Disable the DMA request and context + */ +int fsl_easrc_stop_context(struct fsl_easrc_context *ctx) +{ + struct fsl_easrc *easrc = ctx->easrc; + int ret, val, i; + int size = 0; + int retry = 200; + + regmap_read(easrc->regmap, REG_EASRC_CC(ctx->index), &val); + + if (val & EASRC_CC_EN_MASK) { + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_STOP_MASK, EASRC_CC_STOP); + if (ret) + return ret; + + do { + regmap_read(easrc->regmap, REG_EASRC_SFS(ctx->index), &val); + val &= EASRC_SFS_NSGO_MASK; + size = val >> EASRC_SFS_NSGO_SHIFT; + + /* Read FIFO, drop the data */ + for (i = 0; i < size * ctx->channels; i++) + regmap_read(easrc->regmap, REG_EASRC_RDFIFO(ctx->index), &val); + /* Check RUN_STOP_DONE */ + regmap_read(easrc->regmap, REG_EASRC_IRQF, &val); + if (val & EASRC_IRQF_RSD(1 << ctx->index)) { + /*Clear RUN_STOP_DONE*/ + regmap_write_bits(easrc->regmap, + REG_EASRC_IRQF, + EASRC_IRQF_RSD(1 << ctx->index), + EASRC_IRQF_RSD(1 << ctx->index)); + break; + } + udelay(100); + } while (--retry); + + if (retry == 0) + dev_err(&easrc->pdev->dev, "RUN STOP fail\n"); + } + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_EN_MASK | EASRC_CC_STOP_MASK, 0); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_CC(ctx->index), + EASRC_CC_FWMDE_MASK, 0); + if (ret) + return ret; + + ret = regmap_update_bits(easrc->regmap, + REG_EASRC_COC(ctx->index), + EASRC_COC_FWMDE_MASK, 0); + if (ret) + return ret; + + return 0; +} + +struct dma_chan *fsl_easrc_get_dma_channel(struct fsl_easrc_context *ctx, + bool dir) +{ + struct fsl_easrc *easrc = ctx->easrc; + enum asrc_pair_index index = ctx->index; + char name[8]; + + /* example of dma name: ctx0_rx */ + sprintf(name, "ctx%c_%cx", index + '0', dir == IN ? 'r' : 't'); + + return dma_request_slave_channel(&easrc->pdev->dev, name); +}; +EXPORT_SYMBOL_GPL(fsl_easrc_get_dma_channel); + +static const unsigned int easrc_rates[] = { + 8000, 11025, 12000, 16000, + 22050, 24000, 32000, 44100, + 48000, 64000, 88200, 96000, + 128000, 176400, 192000, 256000, + 352800, 384000, 705600, 768000, +}; + +static const struct snd_pcm_hw_constraint_list easrc_rate_constraints = { + .count = ARRAY_SIZE(easrc_rates), + .list = easrc_rates, +}; + +static int fsl_easrc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &easrc_rate_constraints); +} + +static int fsl_easrc_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_easrc_start_context(ctx); + if (ret) + return ret; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = fsl_easrc_stop_context(ctx); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_easrc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsl_easrc *easrc = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = runtime->private_data; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + snd_pcm_format_t format = params_format(params); + int ret; + + ret = fsl_easrc_request_context(ctx, channels); + if (ret) { + dev_err(dev, "failed to request context\n"); + return ret; + } + + ctx->ctx_streams |= BIT(substream->stream); + + /* set the input and output ratio so we can compute + * the resampling ratio in RS_LOW/HIGH + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ctx->in_params.sample_rate = rate; + ctx->in_params.sample_format = format; + ctx->out_params.sample_rate = easrc->easrc_rate; + ctx->out_params.sample_format = easrc->easrc_format; + } else { + ctx->out_params.sample_rate = rate; + ctx->out_params.sample_format = format; + ctx->in_params.sample_rate = easrc->easrc_rate; + ctx->in_params.sample_format = easrc->easrc_format; + } + + ctx->channels = channels; + ctx->in_params.fifo_wtmk = 0x20; + ctx->out_params.fifo_wtmk = 0x20; + + /* do only rate conversion and keep the same format for input + * and output data + */ + ret = fsl_easrc_set_ctx_format(ctx, + &ctx->in_params.sample_format, + &ctx->out_params.sample_format); + if (ret) { + dev_err(dev, "failed to set format %d", ret); + return ret; + } + + ret = fsl_easrc_config_context(easrc, ctx->index); + if (ret) { + dev_err(dev, "failed to config context\n"); + return ret; + } + + ctx->in_params.iterations = 1; + ctx->in_params.group_len = ctx->channels; + ctx->in_params.access_len = ctx->channels; + ctx->out_params.iterations = 1; + ctx->out_params.group_len = ctx->channels; + ctx->out_params.access_len = ctx->channels; + + ret = fsl_easrc_set_ctx_organziation(ctx); + if (ret) { + dev_err(dev, "failed to set fifo organization\n"); + return ret; + } + + return 0; +} + +static int fsl_easrc_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + int ret; + + if (ctx && (ctx->ctx_streams & BIT(substream->stream))) { + ctx->ctx_streams &= ~BIT(substream->stream); + ret = fsl_easrc_release_context(ctx); + if (ret) + return ret; + } + + return 0; +} + +static struct snd_soc_dai_ops fsl_easrc_dai_ops = { + .startup = fsl_easrc_startup, + .trigger = fsl_easrc_trigger, + .hw_params = fsl_easrc_hw_params, + .hw_free = fsl_easrc_hw_free, +}; + +static int fsl_easrc_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_easrc *easrc = dev_get_drvdata(cpu_dai->dev); + + snd_soc_dai_init_dma_data(cpu_dai, + &easrc->dma_params_tx, + &easrc->dma_params_rx); + return 0; +} + +static struct snd_soc_dai_driver fsl_easrc_dai = { + .probe = fsl_easrc_dai_probe, + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_easrc_component = { + .name = "fsl-easrc-dai", + .controls = fsl_easrc_snd_controls, + .num_controls = ARRAY_SIZE(fsl_easrc_snd_controls), +}; + +static const struct reg_default fsl_easrc_reg_defaults[] = { + {REG_EASRC_WRFIFO(0), 0x00000000}, + {REG_EASRC_WRFIFO(1), 0x00000000}, + {REG_EASRC_WRFIFO(2), 0x00000000}, + {REG_EASRC_WRFIFO(3), 0x00000000}, + {REG_EASRC_RDFIFO(0), 0x00000000}, + {REG_EASRC_RDFIFO(1), 0x00000000}, + {REG_EASRC_RDFIFO(2), 0x00000000}, + {REG_EASRC_RDFIFO(3), 0x00000000}, + {REG_EASRC_CC(0), 0x00000000}, + {REG_EASRC_CC(1), 0x00000000}, + {REG_EASRC_CC(2), 0x00000000}, + {REG_EASRC_CC(3), 0x00000000}, + {REG_EASRC_CCE1(0), 0x00000000}, + {REG_EASRC_CCE1(1), 0x00000000}, + {REG_EASRC_CCE1(2), 0x00000000}, + {REG_EASRC_CCE1(3), 0x00000000}, + {REG_EASRC_CCE2(0), 0x00000000}, + {REG_EASRC_CCE2(1), 0x00000000}, + {REG_EASRC_CCE2(2), 0x00000000}, + {REG_EASRC_CCE2(3), 0x00000000}, + {REG_EASRC_CIA(0), 0x00000000}, + {REG_EASRC_CIA(1), 0x00000000}, + {REG_EASRC_CIA(2), 0x00000000}, + {REG_EASRC_CIA(3), 0x00000000}, + {REG_EASRC_DPCS0R0(0), 0x00000000}, + {REG_EASRC_DPCS0R0(1), 0x00000000}, + {REG_EASRC_DPCS0R0(2), 0x00000000}, + {REG_EASRC_DPCS0R0(3), 0x00000000}, + {REG_EASRC_DPCS0R1(0), 0x00000000}, + {REG_EASRC_DPCS0R1(1), 0x00000000}, + {REG_EASRC_DPCS0R1(2), 0x00000000}, + {REG_EASRC_DPCS0R1(3), 0x00000000}, + {REG_EASRC_DPCS0R2(0), 0x00000000}, + {REG_EASRC_DPCS0R2(1), 0x00000000}, + {REG_EASRC_DPCS0R2(2), 0x00000000}, + {REG_EASRC_DPCS0R2(3), 0x00000000}, + {REG_EASRC_DPCS0R3(0), 0x00000000}, + {REG_EASRC_DPCS0R3(1), 0x00000000}, + {REG_EASRC_DPCS0R3(2), 0x00000000}, + {REG_EASRC_DPCS0R3(3), 0x00000000}, + {REG_EASRC_DPCS1R0(0), 0x00000000}, + {REG_EASRC_DPCS1R0(1), 0x00000000}, + {REG_EASRC_DPCS1R0(2), 0x00000000}, + {REG_EASRC_DPCS1R0(3), 0x00000000}, + {REG_EASRC_DPCS1R1(0), 0x00000000}, + {REG_EASRC_DPCS1R1(1), 0x00000000}, + {REG_EASRC_DPCS1R1(2), 0x00000000}, + {REG_EASRC_DPCS1R1(3), 0x00000000}, + {REG_EASRC_DPCS1R2(0), 0x00000000}, + {REG_EASRC_DPCS1R2(1), 0x00000000}, + {REG_EASRC_DPCS1R2(2), 0x00000000}, + {REG_EASRC_DPCS1R2(3), 0x00000000}, + {REG_EASRC_DPCS1R3(0), 0x00000000}, + {REG_EASRC_DPCS1R3(1), 0x00000000}, + {REG_EASRC_DPCS1R3(2), 0x00000000}, + {REG_EASRC_DPCS1R3(3), 0x00000000}, + {REG_EASRC_COC(0), 0x00000000}, + {REG_EASRC_COC(1), 0x00000000}, + {REG_EASRC_COC(2), 0x00000000}, + {REG_EASRC_COC(3), 0x00000000}, + {REG_EASRC_COA(0), 0x00000000}, + {REG_EASRC_COA(1), 0x00000000}, + {REG_EASRC_COA(2), 0x00000000}, + {REG_EASRC_COA(3), 0x00000000}, + {REG_EASRC_SFS(0), 0x00000000}, + {REG_EASRC_SFS(1), 0x00000000}, + {REG_EASRC_SFS(2), 0x00000000}, + {REG_EASRC_SFS(3), 0x00000000}, + {REG_EASRC_RRL(0), 0x00000000}, + {REG_EASRC_RRL(1), 0x00000000}, + {REG_EASRC_RRL(2), 0x00000000}, + {REG_EASRC_RRL(3), 0x00000000}, + {REG_EASRC_RRH(0), 0x00000000}, + {REG_EASRC_RRH(1), 0x00000000}, + {REG_EASRC_RRH(2), 0x00000000}, + {REG_EASRC_RRH(3), 0x00000000}, + {REG_EASRC_RUC(0), 0x00000000}, + {REG_EASRC_RUC(1), 0x00000000}, + {REG_EASRC_RUC(2), 0x00000000}, + {REG_EASRC_RUC(3), 0x00000000}, + {REG_EASRC_RUR(0), 0x7FFFFFFF}, + {REG_EASRC_RUR(1), 0x7FFFFFFF}, + {REG_EASRC_RUR(2), 0x7FFFFFFF}, + {REG_EASRC_RUR(3), 0x7FFFFFFF}, + {REG_EASRC_RCTCL, 0x00000000}, + {REG_EASRC_RCTCH, 0x00000000}, + {REG_EASRC_PCF(0), 0x00000000}, + {REG_EASRC_PCF(1), 0x00000000}, + {REG_EASRC_PCF(2), 0x00000000}, + {REG_EASRC_PCF(3), 0x00000000}, + {REG_EASRC_CRCM, 0x00000000}, + {REG_EASRC_CRCC, 0x00000000}, + {REG_EASRC_IRQC, 0x00000FFF}, + {REG_EASRC_IRQF, 0x00000000}, + {REG_EASRC_CS0(0), 0x00000000}, + {REG_EASRC_CS0(1), 0x00000000}, + {REG_EASRC_CS0(2), 0x00000000}, + {REG_EASRC_CS0(3), 0x00000000}, + {REG_EASRC_CS1(0), 0x00000000}, + {REG_EASRC_CS1(1), 0x00000000}, + {REG_EASRC_CS1(2), 0x00000000}, + {REG_EASRC_CS1(3), 0x00000000}, + {REG_EASRC_CS2(0), 0x00000000}, + {REG_EASRC_CS2(1), 0x00000000}, + {REG_EASRC_CS2(2), 0x00000000}, + {REG_EASRC_CS2(3), 0x00000000}, + {REG_EASRC_CS3(0), 0x00000000}, + {REG_EASRC_CS3(1), 0x00000000}, + {REG_EASRC_CS3(2), 0x00000000}, + {REG_EASRC_CS3(3), 0x00000000}, + {REG_EASRC_CS4(0), 0x00000000}, + {REG_EASRC_CS4(1), 0x00000000}, + {REG_EASRC_CS4(2), 0x00000000}, + {REG_EASRC_CS4(3), 0x00000000}, + {REG_EASRC_CS5(0), 0x00000000}, + {REG_EASRC_CS5(1), 0x00000000}, + {REG_EASRC_CS5(2), 0x00000000}, + {REG_EASRC_CS5(3), 0x00000000}, + {REG_EASRC_DBGC, 0x00000000}, + {REG_EASRC_DBGS, 0x00000000}, +}; + +static bool fsl_easrc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_EASRC_RDFIFO(0): + case REG_EASRC_RDFIFO(1): + case REG_EASRC_RDFIFO(2): + case REG_EASRC_RDFIFO(3): + case REG_EASRC_CC(0): + case REG_EASRC_CC(1): + case REG_EASRC_CC(2): + case REG_EASRC_CC(3): + case REG_EASRC_CCE1(0): + case REG_EASRC_CCE1(1): + case REG_EASRC_CCE1(2): + case REG_EASRC_CCE1(3): + case REG_EASRC_CCE2(0): + case REG_EASRC_CCE2(1): + case REG_EASRC_CCE2(2): + case REG_EASRC_CCE2(3): + case REG_EASRC_CIA(0): + case REG_EASRC_CIA(1): + case REG_EASRC_CIA(2): + case REG_EASRC_CIA(3): + case REG_EASRC_DPCS0R0(0): + case REG_EASRC_DPCS0R0(1): + case REG_EASRC_DPCS0R0(2): + case REG_EASRC_DPCS0R0(3): + case REG_EASRC_DPCS0R1(0): + case REG_EASRC_DPCS0R1(1): + case REG_EASRC_DPCS0R1(2): + case REG_EASRC_DPCS0R1(3): + case REG_EASRC_DPCS0R2(0): + case REG_EASRC_DPCS0R2(1): + case REG_EASRC_DPCS0R2(2): + case REG_EASRC_DPCS0R2(3): + case REG_EASRC_DPCS0R3(0): + case REG_EASRC_DPCS0R3(1): + case REG_EASRC_DPCS0R3(2): + case REG_EASRC_DPCS0R3(3): + case REG_EASRC_DPCS1R0(0): + case REG_EASRC_DPCS1R0(1): + case REG_EASRC_DPCS1R0(2): + case REG_EASRC_DPCS1R0(3): + case REG_EASRC_DPCS1R1(0): + case REG_EASRC_DPCS1R1(1): + case REG_EASRC_DPCS1R1(2): + case REG_EASRC_DPCS1R1(3): + case REG_EASRC_DPCS1R2(0): + case REG_EASRC_DPCS1R2(1): + case REG_EASRC_DPCS1R2(2): + case REG_EASRC_DPCS1R2(3): + case REG_EASRC_DPCS1R3(0): + case REG_EASRC_DPCS1R3(1): + case REG_EASRC_DPCS1R3(2): + case REG_EASRC_DPCS1R3(3): + case REG_EASRC_COC(0): + case REG_EASRC_COC(1): + case REG_EASRC_COC(2): + case REG_EASRC_COC(3): + case REG_EASRC_COA(0): + case REG_EASRC_COA(1): + case REG_EASRC_COA(2): + case REG_EASRC_COA(3): + case REG_EASRC_SFS(0): + case REG_EASRC_SFS(1): + case REG_EASRC_SFS(2): + case REG_EASRC_SFS(3): + case REG_EASRC_RRL(0): + case REG_EASRC_RRL(1): + case REG_EASRC_RRL(2): + case REG_EASRC_RRL(3): + case REG_EASRC_RRH(0): + case REG_EASRC_RRH(1): + case REG_EASRC_RRH(2): + case REG_EASRC_RRH(3): + case REG_EASRC_RUC(0): + case REG_EASRC_RUC(1): + case REG_EASRC_RUC(2): + case REG_EASRC_RUC(3): + case REG_EASRC_RUR(0): + case REG_EASRC_RUR(1): + case REG_EASRC_RUR(2): + case REG_EASRC_RUR(3): /* fallthrough */ + case REG_EASRC_RCTCL: + case REG_EASRC_RCTCH: + case REG_EASRC_PCF(0): + case REG_EASRC_PCF(1): + case REG_EASRC_PCF(2): + case REG_EASRC_PCF(3): /* fallthrough */ + case REG_EASRC_CRCC: + case REG_EASRC_IRQC: + case REG_EASRC_IRQF: + case REG_EASRC_CS0(0): + case REG_EASRC_CS0(1): + case REG_EASRC_CS0(2): + case REG_EASRC_CS0(3): + case REG_EASRC_CS1(0): + case REG_EASRC_CS1(1): + case REG_EASRC_CS1(2): + case REG_EASRC_CS1(3): + case REG_EASRC_CS2(0): + case REG_EASRC_CS2(1): + case REG_EASRC_CS2(2): + case REG_EASRC_CS2(3): + case REG_EASRC_CS3(0): + case REG_EASRC_CS3(1): + case REG_EASRC_CS3(2): + case REG_EASRC_CS3(3): + case REG_EASRC_CS4(0): + case REG_EASRC_CS4(1): + case REG_EASRC_CS4(2): + case REG_EASRC_CS4(3): + case REG_EASRC_CS5(0): + case REG_EASRC_CS5(1): + case REG_EASRC_CS5(2): + case REG_EASRC_CS5(3): /* fallthrough */ + case REG_EASRC_DBGC: + case REG_EASRC_DBGS: + return true; + default: + return false; + } +} + +static bool fsl_easrc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_EASRC_WRFIFO(0): + case REG_EASRC_WRFIFO(1): + case REG_EASRC_WRFIFO(2): + case REG_EASRC_WRFIFO(3): + case REG_EASRC_CC(0): + case REG_EASRC_CC(1): + case REG_EASRC_CC(2): + case REG_EASRC_CC(3): + case REG_EASRC_CCE1(0): + case REG_EASRC_CCE1(1): + case REG_EASRC_CCE1(2): + case REG_EASRC_CCE1(3): + case REG_EASRC_CCE2(0): + case REG_EASRC_CCE2(1): + case REG_EASRC_CCE2(2): + case REG_EASRC_CCE2(3): + case REG_EASRC_CIA(0): + case REG_EASRC_CIA(1): + case REG_EASRC_CIA(2): + case REG_EASRC_CIA(3): + case REG_EASRC_DPCS0R0(0): + case REG_EASRC_DPCS0R0(1): + case REG_EASRC_DPCS0R0(2): + case REG_EASRC_DPCS0R0(3): + case REG_EASRC_DPCS0R1(0): + case REG_EASRC_DPCS0R1(1): + case REG_EASRC_DPCS0R1(2): + case REG_EASRC_DPCS0R1(3): + case REG_EASRC_DPCS0R2(0): + case REG_EASRC_DPCS0R2(1): + case REG_EASRC_DPCS0R2(2): + case REG_EASRC_DPCS0R2(3): + case REG_EASRC_DPCS0R3(0): + case REG_EASRC_DPCS0R3(1): + case REG_EASRC_DPCS0R3(2): + case REG_EASRC_DPCS0R3(3): + case REG_EASRC_DPCS1R0(0): + case REG_EASRC_DPCS1R0(1): + case REG_EASRC_DPCS1R0(2): + case REG_EASRC_DPCS1R0(3): + case REG_EASRC_DPCS1R1(0): + case REG_EASRC_DPCS1R1(1): + case REG_EASRC_DPCS1R1(2): + case REG_EASRC_DPCS1R1(3): + case REG_EASRC_DPCS1R2(0): + case REG_EASRC_DPCS1R2(1): + case REG_EASRC_DPCS1R2(2): + case REG_EASRC_DPCS1R2(3): + case REG_EASRC_DPCS1R3(0): + case REG_EASRC_DPCS1R3(1): + case REG_EASRC_DPCS1R3(2): + case REG_EASRC_DPCS1R3(3): + case REG_EASRC_COC(0): + case REG_EASRC_COC(1): + case REG_EASRC_COC(2): + case REG_EASRC_COC(3): + case REG_EASRC_COA(0): + case REG_EASRC_COA(1): + case REG_EASRC_COA(2): + case REG_EASRC_COA(3): + case REG_EASRC_RRL(0): + case REG_EASRC_RRL(1): + case REG_EASRC_RRL(2): + case REG_EASRC_RRL(3): + case REG_EASRC_RRH(0): + case REG_EASRC_RRH(1): + case REG_EASRC_RRH(2): + case REG_EASRC_RRH(3): + case REG_EASRC_RUC(0): + case REG_EASRC_RUC(1): + case REG_EASRC_RUC(2): + case REG_EASRC_RUC(3): + case REG_EASRC_RUR(0): + case REG_EASRC_RUR(1): + case REG_EASRC_RUR(2): + case REG_EASRC_RUR(3): /* fallthrough */ + case REG_EASRC_RCTCL: + case REG_EASRC_RCTCH: + case REG_EASRC_PCF(0): + case REG_EASRC_PCF(1): + case REG_EASRC_PCF(2): + case REG_EASRC_PCF(3): /* fallthrough */ + case REG_EASRC_CRCM: + case REG_EASRC_CRCC: + case REG_EASRC_IRQC: + case REG_EASRC_IRQF: + case REG_EASRC_CS0(0): + case REG_EASRC_CS0(1): + case REG_EASRC_CS0(2): + case REG_EASRC_CS0(3): + case REG_EASRC_CS1(0): + case REG_EASRC_CS1(1): + case REG_EASRC_CS1(2): + case REG_EASRC_CS1(3): + case REG_EASRC_CS2(0): + case REG_EASRC_CS2(1): + case REG_EASRC_CS2(2): + case REG_EASRC_CS2(3): + case REG_EASRC_CS3(0): + case REG_EASRC_CS3(1): + case REG_EASRC_CS3(2): + case REG_EASRC_CS3(3): + case REG_EASRC_CS4(0): + case REG_EASRC_CS4(1): + case REG_EASRC_CS4(2): + case REG_EASRC_CS4(3): + case REG_EASRC_CS5(0): + case REG_EASRC_CS5(1): + case REG_EASRC_CS5(2): + case REG_EASRC_CS5(3): /* fallthrough */ + case REG_EASRC_DBGC: + return true; + default: + return false; + } +} + +static bool fsl_easrc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_EASRC_RDFIFO(0): + case REG_EASRC_RDFIFO(1): + case REG_EASRC_RDFIFO(2): + case REG_EASRC_RDFIFO(3): + case REG_EASRC_SFS(0): + case REG_EASRC_SFS(1): + case REG_EASRC_SFS(2): + case REG_EASRC_SFS(3): + case REG_EASRC_IRQF: + case REG_EASRC_DBGS: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_easrc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_EASRC_DBGS, + .reg_defaults = fsl_easrc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_easrc_reg_defaults), + .readable_reg = fsl_easrc_readable_reg, + .volatile_reg = fsl_easrc_volatile_reg, + .writeable_reg = fsl_easrc_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +#include "fsl_easrc_m2m.c" + +void easrc_dump_firmware(struct fsl_easrc *easrc) +{ + struct device *dev = &easrc->pdev->dev; + struct asrc_firmware_hdr *firm = easrc->firmware_hdr; + struct interp_params *interp = easrc->interp; + struct prefil_params *prefil = easrc->prefil; + int i; + + if (firm->magic != FIRMWARE_MAGIC) { + dev_err(dev, "Wrong magic. Something went wrong!"); + return; + } + + dev_dbg(dev, "Firmware v%u dump:\n", firm->firmware_version); + pr_debug("Num prefitler scenarios: %u\n", firm->prefil_scen); + pr_debug("Num interpolation scenarios: %u\n", firm->interp_scen); + pr_debug("\nInterpolation scenarios:\n"); + + for (i = 0; i < firm->interp_scen; i++) { + if (interp[i].magic != FIRMWARE_MAGIC) { + pr_debug("%d. wrong interp magic: %x\n", + i, interp[i].magic); + continue; + } + pr_debug("%d. taps: %u, phases: %u, center: %llu\n", i, + interp[i].num_taps, interp[i].num_phases, + interp[i].center_tap); + } + + for (i = 0; i < firm->prefil_scen; i++) { + if (prefil[i].magic != FIRMWARE_MAGIC) { + pr_debug("%d. wrong prefil magic: %x\n", + i, prefil[i].magic); + continue; + } + pr_debug("%d. insr: %u, outsr: %u, st1: %u, st2: %u\n", i, + prefil[i].insr, prefil[i].outsr, + prefil[i].st1_taps, prefil[i].st2_taps); + } + + dev_dbg(dev, "end of firmware dump\n"); +} + +int easrc_get_firmware(struct fsl_easrc *easrc) +{ + u32 pnum, inum, offset; + int ret; + + if (!easrc) + return -EINVAL; + + ret = request_firmware(&easrc->fw, easrc->fw_name, + &easrc->pdev->dev); + if (ret) + return ret; + + easrc->firmware_hdr = (struct asrc_firmware_hdr *)easrc->fw->data; + pnum = easrc->firmware_hdr->prefil_scen; + inum = easrc->firmware_hdr->interp_scen; + + if (inum) { + offset = sizeof(struct asrc_firmware_hdr); + easrc->interp = + (struct interp_params *)(easrc->fw->data + offset); + } + + if (pnum) { + offset = sizeof(struct asrc_firmware_hdr) + + inum * sizeof(struct interp_params); + easrc->prefil = + (struct prefil_params *)(easrc->fw->data + offset); + } + + return 0; +} + +static irqreturn_t fsl_easrc_isr(int irq, void *dev_id) +{ + struct fsl_easrc *easrc = (struct fsl_easrc *)dev_id; + struct device *dev = &easrc->pdev->dev; + + /* TODO treat the interrupt */ + dev_err(dev, "interrupt\n"); + + return IRQ_HANDLED; +} + +static const struct of_device_id fsl_easrc_dt_ids[] = { + { .compatible = "fsl,imx8mn-easrc",}, + {} +}; + +MODULE_DEVICE_TABLE(of, fsl_easrc_dt_ids); + +static int fsl_easrc_probe(struct platform_device *pdev) +{ + struct fsl_easrc *easrc; + struct resource *res; + struct device_node *np; + void __iomem *regs; + int ret, irq; + u32 rstap = 0; + u32 width; + + easrc = devm_kzalloc(&pdev->dev, sizeof(*easrc), GFP_KERNEL); + if (!easrc) + return -ENOMEM; + + strncpy(easrc->name, "mxc_asrc", sizeof(easrc->name) - 1); + + easrc->pdev = pdev; + np = pdev->dev.of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) { + dev_err(&pdev->dev, "failed ioremap\n"); + return PTR_ERR(regs); + } + + easrc->paddr = res->start; + + easrc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, regs, + &fsl_easrc_regmap_config); + if (IS_ERR(easrc->regmap)) { + dev_err(&pdev->dev, "failed to init regmap"); + return PTR_ERR(easrc->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", + dev_name(&pdev->dev)); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_easrc_isr, 0, + dev_name(&pdev->dev), easrc); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret); + return ret; + } + + easrc->mem_clk = devm_clk_get(&pdev->dev, "mem"); + if (IS_ERR(easrc->mem_clk)) { + dev_err(&pdev->dev, "failed to get mem clock\n"); + return PTR_ERR(easrc->mem_clk); + } + + /*Set default value*/ + easrc->chn_avail = 32; + easrc->const_coeff = 0x3FF0000000000000; + + of_property_read_u32(np, "fsl,asrc-rstap", &rstap); + switch (rstap) { + case 32: + easrc->rs_num_taps = EASRC_RS_32_TAPS; + break; + case 64: + easrc->rs_num_taps = EASRC_RS_64_TAPS; + break; + case 128: + easrc->rs_num_taps = EASRC_RS_128_TAPS; + break; + default: + easrc->rs_num_taps = EASRC_RS_32_TAPS; + break; + } + + ret = of_property_read_u32(np, "fsl,asrc-rate", + &easrc->easrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to asrc rate\n"); + return ret; + } + + ret = of_property_read_u32(np, "fsl,asrc-width", + &width); + if (ret) { + dev_err(&pdev->dev, "failed to asrc width\n"); + return ret; + } + + if (width != 16 && width != 24 && width != 32 && width != 20) { + dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n"); + width = 24; + } + + if (width == 24) + easrc->easrc_format = SNDRV_PCM_FORMAT_S24_LE; + else if (width == 16) + easrc->easrc_format = SNDRV_PCM_FORMAT_S16_LE; + else + easrc->easrc_format = SNDRV_PCM_FORMAT_S32_LE; + + platform_set_drvdata(pdev, easrc); + pm_runtime_enable(&pdev->dev); + + spin_lock_init(&easrc->lock); + + regcache_cache_only(easrc->regmap, true); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_easrc_component, + &fsl_easrc_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &fsl_easrc_dma_component, + NULL, 0); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC platform\n"); + return ret; + } + + ret = fsl_easrc_m2m_init(easrc); + if (ret) { + dev_err(&pdev->dev, "failed to init m2m device %d\n", ret); + return ret; + } + + ret = of_property_read_string(np, + "fsl,easrc-ram-script-name", + &easrc->fw_name); + if (ret) { + dev_err(&pdev->dev, "failed to get firmware name\n"); + return ret; + } + + return 0; +} + +static int fsl_easrc_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_easrc_runtime_suspend(struct device *dev) +{ + struct fsl_easrc *easrc = dev_get_drvdata(dev); + unsigned long lock_flags; + + regcache_cache_only(easrc->regmap, true); + + clk_disable_unprepare(easrc->mem_clk); + + spin_lock_irqsave(&easrc->lock, lock_flags); + easrc->firmware_loaded = 0; + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + return 0; +} + +static int fsl_easrc_runtime_resume(struct device *dev) +{ + struct fsl_easrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_context *ctx; + unsigned long lock_flags; + int ret; + int i; + + ret = clk_prepare_enable(easrc->mem_clk); + if (ret) + return ret; + + regcache_cache_only(easrc->regmap, false); + regcache_mark_dirty(easrc->regmap); + regcache_sync(easrc->regmap); + + spin_lock_irqsave(&easrc->lock, lock_flags); + if (easrc->firmware_loaded) { + spin_unlock_irqrestore(&easrc->lock, lock_flags); + goto skip_load; + } + easrc->firmware_loaded = 1; + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + ret = easrc_get_firmware(easrc); + if (ret) { + dev_err(dev, "failed to get firmware\n"); + goto disable_mem_clk; + } + + /* Write Resampling Coefficients + * The coefficient RAM must be configured prior to beginning of + * any context processing within the ASRC + */ + ret = fsl_easrc_resampler_config(easrc); + if (ret) { + dev_err(dev, "resampler config failed\n"); + goto disable_mem_clk; + } + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + ctx = easrc->ctx[i]; + if (ctx) { + set_rs_ratio(ctx); + ctx->out_missed_sample = ctx->in_filled_sample * + ctx->out_params.sample_rate / + ctx->in_params.sample_rate; + if (ctx->in_filled_sample * ctx->out_params.sample_rate + % ctx->in_params.sample_rate != 0) + ctx->out_missed_sample += 1; + + ret = write_pf_coeff_mem(easrc, i, + ctx->st1_coeff, + ctx->st1_num_taps, + ctx->st1_addexp); + if (ret) + goto disable_mem_clk; + + ret = write_pf_coeff_mem(easrc, i, + ctx->st2_coeff, + ctx->st2_num_taps, + ctx->st2_addexp); + if (ret) + goto disable_mem_clk; + } + } + +skip_load: + return 0; + +disable_mem_clk: + clk_disable_unprepare(easrc->mem_clk); + return ret; +} +#endif /*CONFIG_PM*/ + +#ifdef CONFIG_PM_SLEEP +static int fsl_easrc_suspend(struct device *dev) +{ + struct fsl_easrc *easrc = dev_get_drvdata(dev); + int ret; + + fsl_easrc_m2m_suspend(easrc); + + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int fsl_easrc_resume(struct device *dev) +{ + struct fsl_easrc *easrc = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + + fsl_easrc_m2m_resume(easrc); + + return ret; +} +#endif /*CONFIG_PM_SLEEP*/ + +static const struct dev_pm_ops fsl_easrc_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_easrc_runtime_suspend, + fsl_easrc_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_easrc_suspend, + fsl_easrc_resume) +}; + +static struct platform_driver fsl_easrc_driver = { + .probe = fsl_easrc_probe, + .remove = fsl_easrc_remove, + .driver = { + .name = "fsl-easrc", + .pm = &fsl_easrc_pm_ops, + .of_match_table = fsl_easrc_dt_ids, + }, +}; +module_platform_driver(fsl_easrc_driver); + +MODULE_DESCRIPTION("NXP Enhanced Asynchronous Sample Rate (eASRC) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_easrc.h b/sound/soc/fsl/fsl_easrc.h new file mode 100644 index 000000000000..aeeb8bcdc12a --- /dev/null +++ b/sound/soc/fsl/fsl_easrc.h @@ -0,0 +1,698 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 NXP + */ + +#ifndef _FSL_EASRC_H +#define _FSL_EASRC_H + +#include <sound/asound.h> +#include <uapi/linux/mxc_asrc.h> +#include <linux/miscdevice.h> +#include <linux/platform_data/dma-imx.h> + +/* EASRC Register Map */ + +/* ASRC Input Write FIFO */ +#define REG_EASRC_WRFIFO(ctx) (0x000 + 4 * (ctx)) +/* ASRC Output Read FIFO */ +#define REG_EASRC_RDFIFO(ctx) (0x010 + 4 * (ctx)) +/* ASRC Context Control */ +#define REG_EASRC_CC(ctx) (0x020 + 4 * (ctx)) +/* ASRC Context Control Extended 1 */ +#define REG_EASRC_CCE1(ctx) (0x030 + 4 * (ctx)) +/* ASRC Context Control Extended 2 */ +#define REG_EASRC_CCE2(ctx) (0x040 + 4 * (ctx)) +/* ASRC Control Input Access */ +#define REG_EASRC_CIA(ctx) (0x050 + 4 * (ctx)) +/* ASRC Datapath Processor Control Slot0 */ +#define REG_EASRC_DPCS0R0(ctx) (0x060 + 4 * (ctx)) +#define REG_EASRC_DPCS0R1(ctx) (0x070 + 4 * (ctx)) +#define REG_EASRC_DPCS0R2(ctx) (0x080 + 4 * (ctx)) +#define REG_EASRC_DPCS0R3(ctx) (0x090 + 4 * (ctx)) +/* ASRC Datapath Processor Control Slot1 */ +#define REG_EASRC_DPCS1R0(ctx) (0x0A0 + 4 * (ctx)) +#define REG_EASRC_DPCS1R1(ctx) (0x0B0 + 4 * (ctx)) +#define REG_EASRC_DPCS1R2(ctx) (0x0C0 + 4 * (ctx)) +#define REG_EASRC_DPCS1R3(ctx) (0x0D0 + 4 * (ctx)) +/* ASRC Context Output Control */ +#define REG_EASRC_COC(ctx) (0x0E0 + 4 * (ctx)) +/* ASRC Control Output Access */ +#define REG_EASRC_COA(ctx) (0x0F0 + 4 * (ctx)) +/* ASRC Sample FIFO Status */ +#define REG_EASRC_SFS(ctx) (0x100 + 4 * (ctx)) +/* ASRC Resampling Ratio Low */ +#define REG_EASRC_RRL(ctx) (0x110 + 8 * (ctx)) +/* ASRC Resampling Ratio High */ +#define REG_EASRC_RRH(ctx) (0x114 + 8 * (ctx)) +/* ASRC Resampling Ratio Update Control */ +#define REG_EASRC_RUC(ctx) (0x130 + 4 * (ctx)) +/* ASRC Resampling Ratio Update Rate */ +#define REG_EASRC_RUR(ctx) (0x140 + 4 * (ctx)) +/* ASRC Resampling Center Tap Coefficient Low */ +#define REG_EASRC_RCTCL (0x150) +/* ASRC Resampling Center Tap Coefficient High */ +#define REG_EASRC_RCTCH (0x154) +/* ASRC Prefilter Coefficient FIFO */ +#define REG_EASRC_PCF(ctx) (0x160 + 4 * (ctx)) +/* ASRC Context Resampling Coefficient Memory */ +#define REG_EASRC_CRCM 0x170 +/* ASRC Context Resampling Coefficient Control*/ +#define REG_EASRC_CRCC 0x174 +/* ASRC Interrupt Control */ +#define REG_EASRC_IRQC 0x178 +/* ASRC Interrupt Status Flags */ +#define REG_EASRC_IRQF 0x17C +/* ASRC Channel Status 0 */ +#define REG_EASRC_CS0(ctx) (0x180 + 4 * (ctx)) +/* ASRC Channel Status 1 */ +#define REG_EASRC_CS1(ctx) (0x190 + 4 * (ctx)) +/* ASRC Channel Status 2 */ +#define REG_EASRC_CS2(ctx) (0x1A0 + 4 * (ctx)) +/* ASRC Channel Status 3 */ +#define REG_EASRC_CS3(ctx) (0x1B0 + 4 * (ctx)) +/* ASRC Channel Status 4 */ +#define REG_EASRC_CS4(ctx) (0x1C0 + 4 * (ctx)) +/* ASRC Channel Status 5 */ +#define REG_EASRC_CS5(ctx) (0x1D0 + 4 * (ctx)) +/* ASRC Debug Control Register */ +#define REG_EASRC_DBGC 0x1E0 +/* ASRC Debug Status Register */ +#define REG_EASRC_DBGS 0x1E4 + +#define REG_EASRC_FIFO(x, ctx) (x == IN ? REG_EASRC_WRFIFO(ctx) \ + : REG_EASRC_RDFIFO(ctx)) + +/* ASRC Context Control (CC) */ +#define EASRC_CC_EN_SHIFT 31 +#define EASRC_CC_EN_MASK BIT(EASRC_CC_EN_SHIFT) +#define EASRC_CC_EN BIT(EASRC_CC_EN_SHIFT) +#define EASRC_CC_STOP_SHIFT 29 +#define EASRC_CC_STOP_MASK BIT(EASRC_CC_STOP_SHIFT) +#define EASRC_CC_STOP BIT(EASRC_CC_STOP_SHIFT) +#define EASRC_CC_FWMDE_SHIFT 28 +#define EASRC_CC_FWMDE_MASK BIT(EASRC_CC_FWMDE_SHIFT) +#define EASRC_CC_FWMDE BIT(EASRC_CC_FWMDE_SHIFT) +#define EASRC_CC_FIFO_WTMK_SHIFT 16 +#define EASRC_CC_FIFO_WTMK_WIDTH 7 +#define EASRC_CC_FIFO_WTMK_MASK ((BIT(EASRC_CC_FIFO_WTMK_WIDTH) - 1) \ + << EASRC_CC_FIFO_WTMK_SHIFT) +#define EASRC_CC_FIFO_WTMK(v) (((v) << EASRC_CC_FIFO_WTMK_SHIFT) \ + & EASRC_CC_FIFO_WTMK_MASK) +#define EASRC_CC_SAMPLE_POS_SHIFT 11 +#define EASRC_CC_SAMPLE_POS_WIDTH 5 +#define EASRC_CC_SAMPLE_POS_MASK ((BIT(EASRC_CC_SAMPLE_POS_WIDTH) - 1) \ + << EASRC_CC_SAMPLE_POS_SHIFT) +#define EASRC_CC_SAMPLE_POS(v) (((v) << EASRC_CC_SAMPLE_POS_SHIFT) \ + & EASRC_CC_SAMPLE_POS_MASK) +#define EASRC_CC_ENDIANNESS_SHIFT 10 +#define EASRC_CC_ENDIANNESS_MASK BIT(EASRC_CC_ENDIANNESS_SHIFT) +#define EASRC_CC_ENDIANNESS BIT(EASRC_CC_ENDIANNESS_SHIFT) +#define EASRC_CC_BPS_SHIFT 8 +#define EASRC_CC_BPS_WIDTH 2 +#define EASRC_CC_BPS_MASK ((BIT(EASRC_CC_BPS_WIDTH) - 1) \ + << EASRC_CC_BPS_SHIFT) +#define EASRC_CC_BPS(v) (((v) << EASRC_CC_BPS_SHIFT) \ + & EASRC_CC_BPS_MASK) +#define EASRC_CC_FMT_SHIFT 7 +#define EASRC_CC_FMT_MASK BIT(EASRC_CC_FMT_SHIFT) +#define EASRC_CC_FMT BIT(EASRC_CC_FMT_SHIFT) +#define EASRC_CC_INSIGN_SHIFT 6 +#define EASRC_CC_INSIGN_MASK BIT(EASRC_CC_INSIGN_SHIFT) +#define EASRC_CC_INSIGN BIT(EASRC_CC_INSIGN_SHIFT) +#define EASRC_CC_CHEN_SHIFT 0 +#define EASRC_CC_CHEN_WIDTH 5 +#define EASRC_CC_CHEN_MASK ((BIT(EASRC_CC_CHEN_WIDTH) - 1) \ + << EASRC_CC_CHEN_SHIFT) +#define EASRC_CC_CHEN(v) (((v) << EASRC_CC_CHEN_SHIFT) \ + & EASRC_CC_CHEN_MASK) + +/* ASRC Context Control Extended 1 (CCE1) */ +#define EASRC_CCE1_COEF_WS_SHIFT 25 +#define EASRC_CCE1_COEF_WS_MASK BIT(EASRC_CCE1_COEF_WS_SHIFT) +#define EASRC_CCE1_COEF_WS BIT(EASRC_CCE1_COEF_WS_SHIFT) +#define EASRC_CCE1_COEF_MEM_RST_SHIFT 24 +#define EASRC_CCE1_COEF_MEM_RST_MASK BIT(EASRC_CCE1_COEF_MEM_RST_SHIFT) +#define EASRC_CCE1_COEF_MEM_RST BIT(EASRC_CCE1_COEF_MEM_RST_SHIFT) +#define EASRC_CCE1_PF_EXP_SHIFT 16 +#define EASRC_CCE1_PF_EXP_WIDTH 8 +#define EASRC_CCE1_PF_EXP_MASK ((BIT(EASRC_CCE1_PF_EXP_WIDTH) - 1) \ + << EASRC_CCE1_PF_EXP_SHIFT) +#define EASRC_CCE1_PF_EXP(v) (((v) << EASRC_CCE1_PF_EXP_SHIFT) \ + & EASRC_CCE1_PF_EXP_MASK) +#define EASRC_CCE1_PF_ST1_WBFP_SHIFT 9 +#define EASRC_CCE1_PF_ST1_WBFP_MASK BIT(EASRC_CCE1_PF_ST1_WBFP_SHIFT) +#define EASRC_CCE1_PF_ST1_WBFP BIT(EASRC_CCE1_PF_ST1_WBFP_SHIFT) +#define EASRC_CCE1_PF_TSEN_SHIFT 8 +#define EASRC_CCE1_PF_TSEN_MASK BIT(EASRC_CCE1_PF_TSEN_SHIFT) +#define EASRC_CCE1_PF_TSEN BIT(EASRC_CCE1_PF_TSEN_SHIFT) +#define EASRC_CCE1_RS_BYPASS_SHIFT 7 +#define EASRC_CCE1_RS_BYPASS_MASK BIT(EASRC_CCE1_RS_BYPASS_SHIFT) +#define EASRC_CCE1_RS_BYPASS BIT(EASRC_CCE1_RS_BYPASS_SHIFT) +#define EASRC_CCE1_PF_BYPASS_SHIFT 6 +#define EASRC_CCE1_PF_BYPASS_MASK BIT(EASRC_CCE1_PF_BYPASS_SHIFT) +#define EASRC_CCE1_PF_BYPASS BIT(EASRC_CCE1_PF_BYPASS_SHIFT) +#define EASRC_CCE1_RS_STOP_SHIFT 5 +#define EASRC_CCE1_RS_STOP_MASK BIT(EASRC_CCE1_RS_STOP_SHIFT) +#define EASRC_CCE1_RS_STOP BIT(EASRC_CCE1_RS_STOP_SHIFT) +#define EASRC_CCE1_PF_STOP_SHIFT 4 +#define EASRC_CCE1_PF_STOP_MASK BIT(EASRC_CCE1_PF_STOP_SHIFT) +#define EASRC_CCE1_PF_STOP BIT(EASRC_CCE1_PF_STOP_SHIFT) +#define EASRC_CCE1_RS_INIT_SHIFT 2 +#define EASRC_CCE1_RS_INIT_WIDTH 2 +#define EASRC_CCE1_RS_INIT_MASK ((BIT(EASRC_CCE1_RS_INIT_WIDTH) - 1) \ + << EASRC_CCE1_RS_INIT_SHIFT) +#define EASRC_CCE1_RS_INIT(v) (((v) << EASRC_CCE1_RS_INIT_SHIFT) \ + & EASRC_CCE1_RS_INIT_MASK) +#define EASRC_CCE1_PF_INIT_SHIFT 0 +#define EASRC_CCE1_PF_INIT_WIDTH 2 +#define EASRC_CCE1_PF_INIT_MASK ((BIT(EASRC_CCE1_PF_INIT_WIDTH) - 1) \ + << EASRC_CCE1_PF_INIT_SHIFT) +#define EASRC_CCE1_PF_INIT(v) (((v) << EASRC_CCE1_PF_INIT_SHIFT) \ + & EASRC_CCE1_PF_INIT_MASK) + +/* ASRC Context Control Extended 2 (CCE2) */ +#define EASRC_CCE2_ST2_TAPS_SHIFT 16 +#define EASRC_CCE2_ST2_TAPS_WIDTH 9 +#define EASRC_CCE2_ST2_TAPS_MASK ((BIT(EASRC_CCE2_ST2_TAPS_WIDTH) - 1) \ + << EASRC_CCE2_ST2_TAPS_SHIFT) +#define EASRC_CCE2_ST2_TAPS(v) (((v) << EASRC_CCE2_ST2_TAPS_SHIFT) \ + & EASRC_CCE2_ST2_TAPS_MASK) +#define EASRC_CCE2_ST1_TAPS_SHIFT 0 +#define EASRC_CCE2_ST1_TAPS_WIDTH 9 +#define EASRC_CCE2_ST1_TAPS_MASK ((BIT(EASRC_CCE2_ST1_TAPS_WIDTH) - 1) \ + << EASRC_CCE2_ST1_TAPS_SHIFT) +#define EASRC_CCE2_ST1_TAPS(v) (((v) << EASRC_CCE2_ST1_TAPS_SHIFT) \ + & EASRC_CCE2_ST1_TAPS_MASK) + +/* ASRC Control Input Access (CIA) */ +#define EASRC_CIA_ITER_SHIFT 16 +#define EASRC_CIA_ITER_WIDTH 6 +#define EASRC_CIA_ITER_MASK ((BIT(EASRC_CIA_ITER_WIDTH) - 1) \ + << EASRC_CIA_ITER_SHIFT) +#define EASRC_CIA_ITER(v) (((v) << EASRC_CIA_ITER_SHIFT) \ + & EASRC_CIA_ITER_MASK) +#define EASRC_CIA_GRLEN_SHIFT 8 +#define EASRC_CIA_GRLEN_WIDTH 6 +#define EASRC_CIA_GRLEN_MASK ((BIT(EASRC_CIA_GRLEN_WIDTH) - 1) \ + << EASRC_CIA_GRLEN_SHIFT) +#define EASRC_CIA_GRLEN(v) (((v) << EASRC_CIA_GRLEN_SHIFT) \ + & EASRC_CIA_GRLEN_MASK) +#define EASRC_CIA_ACCLEN_SHIFT 0 +#define EASRC_CIA_ACCLEN_WIDTH 6 +#define EASRC_CIA_ACCLEN_MASK ((BIT(EASRC_CIA_ACCLEN_WIDTH) - 1) \ + << EASRC_CIA_ACCLEN_SHIFT) +#define EASRC_CIA_ACCLEN(v) (((v) << EASRC_CIA_ACCLEN_SHIFT) \ + & EASRC_CIA_ACCLEN_MASK) + +/* ASRC Datapath Processor Control Slot0 Register0 (DPCS0R0) */ +#define EASRC_DPCS0R0_MAXCH_SHIFT 24 +#define EASRC_DPCS0R0_MAXCH_WIDTH 5 +#define EASRC_DPCS0R0_MAXCH_MASK ((BIT(EASRC_DPCS0R0_MAXCH_WIDTH) - 1) \ + << EASRC_DPCS0R0_MAXCH_SHIFT) +#define EASRC_DPCS0R0_MAXCH(v) (((v) << EASRC_DPCS0R0_MAXCH_SHIFT) \ + & EASRC_DPCS0R0_MAXCH_MASK) +#define EASRC_DPCS0R0_MINCH_SHIFT 16 +#define EASRC_DPCS0R0_MINCH_WIDTH 5 +#define EASRC_DPCS0R0_MINCH_MASK ((BIT(EASRC_DPCS0R0_MINCH_WIDTH) - 1) \ + << EASRC_DPCS0R0_MINCH_SHIFT) +#define EASRC_DPCS0R0_MINCH(v) (((v) << EASRC_DPCS0R0_MINCH_SHIFT) \ + & EASRC_DPCS0R0_MINCH_MASK) +#define EASRC_DPCS0R0_NUMCH_SHIFT 8 +#define EASRC_DPCS0R0_NUMCH_WIDTH 5 +#define EASRC_DPCS0R0_NUMCH_MASK ((BIT(EASRC_DPCS0R0_NUMCH_WIDTH) - 1) \ + << EASRC_DPCS0R0_NUMCH_SHIFT) +#define EASRC_DPCS0R0_NUMCH(v) (((v) << EASRC_DPCS0R0_NUMCH_SHIFT) \ + & EASRC_DPCS0R0_NUMCH_MASK) +#define EASRC_DPCS0R0_CTXNUM_SHIFT 1 +#define EASRC_DPCS0R0_CTXNUM_WIDTH 2 +#define EASRC_DPCS0R0_CTXNUM_MASK ((BIT(EASRC_DPCS0R0_CTXNUM_WIDTH) - 1) \ + << EASRC_DPCS0R0_CTXNUM_SHIFT) +#define EASRC_DPCS0R0_CTXNUM(v) (((v) << EASRC_DPCS0R0_CTXNUM_SHIFT) \ + & EASRC_DPCS0R0_CTXNUM_MASK) +#define EASRC_DPCS0R0_EN_SHIFT 0 +#define EASRC_DPCS0R0_EN_MASK BIT(EASRC_DPCS0R0_EN_SHIFT) +#define EASRC_DPCS0R0_EN BIT(EASRC_DPCS0R0_EN_SHIFT) + +/* ASRC Datapath Processor Control Slot0 Register1 (DPCS0R1) */ +#define EASRC_DPCS0R1_ST1_EXP_SHIFT 0 +#define EASRC_DPCS0R1_ST1_EXP_WIDTH 13 +#define EASRC_DPCS0R1_ST1_EXP_MASK ((BIT(EASRC_DPCS0R1_ST1_EXP_WIDTH) - 1) \ + << EASRC_DPCS0R1_ST1_EXP_SHIFT) +#define EASRC_DPCS0R1_ST1_EXP(v) (((v) << EASRC_DPCS0R1_ST1_EXP_SHIFT) \ + & EASRC_DPCS0R1_ST1_EXP_MASK) + +/* ASRC Datapath Processor Control Slot0 Register2 (DPCS0R2) */ +#define EASRC_DPCS0R2_ST1_MA_SHIFT 16 +#define EASRC_DPCS0R2_ST1_MA_WIDTH 13 +#define EASRC_DPCS0R2_ST1_MA_MASK ((BIT(EASRC_DPCS0R2_ST1_MA_WIDTH) - 1) \ + << EASRC_DPCS0R2_ST1_MA_SHIFT) +#define EASRC_DPCS0R2_ST1_MA(v) (((v) << EASRC_DPCS0R2_ST1_MA_SHIFT) \ + & EASRC_DPCS0R2_ST1_MA_MASK) +#define EASRC_DPCS0R2_ST1_SA_SHIFT 0 +#define EASRC_DPCS0R2_ST1_SA_WIDTH 13 +#define EASRC_DPCS0R2_ST1_SA_MASK ((BIT(EASRC_DPCS0R2_ST1_SA_WIDTH) - 1) \ + << EASRC_DPCS0R2_ST1_SA_SHIFT) +#define EASRC_DPCS0R2_ST1_SA(v) (((v) << EASRC_DPCS0R2_ST1_SA_SHIFT) \ + & EASRC_DPCS0R2_ST1_SA_MASK) + +/* ASRC Datapath Processor Control Slot0 Register3 (DPCS0R3) */ +#define EASRC_DPCS0R3_ST2_MA_SHIFT 16 +#define EASRC_DPCS0R3_ST2_MA_WIDTH 13 +#define EASRC_DPCS0R3_ST2_MA_MASK ((BIT(EASRC_DPCS0R3_ST2_MA_WIDTH) - 1) \ + << EASRC_DPCS0R3_ST2_MA_SHIFT) +#define EASRC_DPCS0R3_ST2_MA(v) (((v) << EASRC_DPCS0R3_ST2_MA_SHIFT) \ + & EASRC_DPCS0R3_ST2_MA_MASK) +#define EASRC_DPCS0R3_ST2_SA_SHIFT 0 +#define EASRC_DPCS0R3_ST2_SA_WIDTH 13 +#define EASRC_DPCS0R3_ST2_SA_MASK ((BIT(EASRC_DPCS0R3_ST2_SA_WIDTH) - 1) \ + << EASRC_DPCS0R3_ST2_SA_SHIFT) +#define EASRC_DPCS0R3_ST2_SA(v) (((v) << EASRC_DPCS0R3_ST2_SA_SHIFT) \ + & EASRC_DPCS0R3_ST2_SA_MASK) + +/* ASRC Context Output Control (COC) */ +#define EASRC_COC_FWMDE_SHIFT 28 +#define EASRC_COC_FWMDE_MASK BIT(EASRC_COC_FWMDE_SHIFT) +#define EASRC_COC_FWMDE BIT(EASRC_COC_FWMDE_SHIFT) +#define EASRC_COC_FIFO_WTMK_SHIFT 16 +#define EASRC_COC_FIFO_WTMK_WIDTH 7 +#define EASRC_COC_FIFO_WTMK_MASK ((BIT(EASRC_COC_FIFO_WTMK_WIDTH) - 1) \ + << EASRC_COC_FIFO_WTMK_SHIFT) +#define EASRC_COC_FIFO_WTMK(v) (((v) << EASRC_COC_FIFO_WTMK_SHIFT) \ + & EASRC_COC_FIFO_WTMK_MASK) +#define EASRC_COC_SAMPLE_POS_SHIFT 11 +#define EASRC_COC_SAMPLE_POS_WIDTH 5 +#define EASRC_COC_SAMPLE_POS_MASK ((BIT(EASRC_COC_SAMPLE_POS_WIDTH) - 1) \ + << EASRC_COC_SAMPLE_POS_SHIFT) +#define EASRC_COC_SAMPLE_POS(v) (((v) << EASRC_COC_SAMPLE_POS_SHIFT) \ + & EASRC_COC_SAMPLE_POS_MASK) +#define EASRC_COC_ENDIANNESS_SHIFT 10 +#define EASRC_COC_ENDIANNESS_MASK BIT(EASRC_COC_ENDIANNESS_SHIFT) +#define EASRC_COC_ENDIANNESS BIT(EASRC_COC_ENDIANNESS_SHIFT) +#define EASRC_COC_BPS_SHIFT 8 +#define EASRC_COC_BPS_WIDTH 2 +#define EASRC_COC_BPS_MASK ((BIT(EASRC_COC_BPS_WIDTH) - 1) \ + << EASRC_COC_BPS_SHIFT) +#define EASRC_COC_BPS(v) (((v) << EASRC_COC_BPS_SHIFT) \ + & EASRC_COC_BPS_MASK) +#define EASRC_COC_FMT_SHIFT 7 +#define EASRC_COC_FMT_MASK BIT(EASRC_COC_FMT_SHIFT) +#define EASRC_COC_FMT BIT(EASRC_COC_FMT_SHIFT) +#define EASRC_COC_OUTSIGN_SHIFT 6 +#define EASRC_COC_OUTSIGN_MASK BIT(EASRC_COC_OUTSIGN_SHIFT) +#define EASRC_COC_OUTSIGN_OUT BIT(EASRC_COC_OUTSIGN_SHIFT) +#define EASRC_COC_IEC_VDATA_SHIFT 2 +#define EASRC_COC_IEC_VDATA_MASK BIT(EASRC_COC_IEC_VDATA_SHIFT) +#define EASRC_COC_IEC_VDATA BIT(EASRC_COC_IEC_VDATA_SHIFT) +#define EASRC_COC_IEC_EN_SHIFT 1 +#define EASRC_COC_IEC_EN_MASK BIT(EASRC_COC_IEC_EN_SHIFT) +#define EASRC_COC_IEC_EN BIT(EASRC_COC_IEC_EN_SHIFT) +#define EASRC_COC_DITHER_EN_SHIFT 0 +#define EASRC_COC_DITHER_EN_MASK BIT(EASRC_COC_DITHER_EN_SHIFT) +#define EASRC_COC_DITHER_EN BIT(EASRC_COC_DITHER_EN_SHIFT) + +/* ASRC Control Output Access (COA) */ +#define EASRC_COA_ITER_SHIFT 16 +#define EASRC_COA_ITER_WIDTH 6 +#define EASRC_COA_ITER_MASK ((BIT(EASRC_COA_ITER_WIDTH) - 1) \ + << EASRC_COA_ITER_SHIFT) +#define EASRC_COA_ITER(v) (((v) << EASRC_COA_ITER_SHIFT) \ + & EASRC_COA_ITER_MASK) +#define EASRC_COA_GRLEN_SHIFT 8 +#define EASRC_COA_GRLEN_WIDTH 6 +#define EASRC_COA_GRLEN_MASK ((BIT(EASRC_COA_GRLEN_WIDTH) - 1) \ + << EASRC_COA_GRLEN_SHIFT) +#define EASRC_COA_GRLEN(v) (((v) << EASRC_COA_GRLEN_SHIFT) \ + & EASRC_COA_GRLEN_MASK) +#define EASRC_COA_ACCLEN_SHIFT 0 +#define EASRC_COA_ACCLEN_WIDTH 6 +#define EASRC_COA_ACCLEN_MASK ((BIT(EASRC_COA_ACCLEN_WIDTH) - 1) \ + << EASRC_COA_ACCLEN_SHIFT) +#define EASRC_COA_ACCLEN(v) (((v) << EASRC_COA_ACCLEN_SHIFT) \ + & EASRC_COA_ACCLEN_MASK) + +/* ASRC Sample FIFO Status (SFS) */ +#define EASRC_SFS_IWTMK_SHIFT 23 +#define EASRC_SFS_IWTMK_MASK BIT(EASRC_SFS_IWTMK_SHIFT) +#define EASRC_SFS_IWTMK BIT(EASRC_SFS_IWTMK_SHIFT) +#define EASRC_SFS_NSGI_SHIFT 16 +#define EASRC_SFS_NSGI_WIDTH 7 +#define EASRC_SFS_NSGI_MASK ((BIT(EASRC_SFS_NSGI_WIDTH) - 1) \ + << EASRC_SFS_NSGI_SHIFT) +#define EASRC_SFS_NSGI(v) (((v) << EASRC_SFS_NSGI_SHIFT) \ + & EASRC_SFS_NSGI_MASK) +#define EASRC_SFS_OWTMK_SHIFT 7 +#define EASRC_SFS_OWTMK_MASK BIT(EASRC_SFS_OWTMK_SHIFT) +#define EASRC_SFS_OWTMK BIT(EASRC_SFS_OWTMK_SHIFT) +#define EASRC_SFS_NSGO_SHIFT 0 +#define EASRC_SFS_NSGO_WIDTH 7 +#define EASRC_SFS_NSGO_MASK ((BIT(EASRC_SFS_NSGO_WIDTH) - 1) \ + << EASRC_SFS_NSGO_SHIFT) +#define EASRC_SFS_NSGO(v) (((v) << EASRC_SFS_NSGO_SHIFT) \ + & EASRC_SFS_NSGO_MASK) + +/* ASRC Resampling Ratio Low (RRL) */ +#define EASRC_RRL_RS_RL_SHIFT 0 +#define EASRC_RRL_RS_RL_WIDTH 32 +#define EASRC_RRL_RS_RL_MASK ((BIT(EASRC_RRL_RS_RL_WIDTH) - 1) \ + << EASRC_RRL_RS_RL_SHIFT) +#define EASRC_RRL_RS_RL(v) (((v) << EASRC_RRL_RS_RL_SHIFT) \ + & EASRC_RRL_RS_RL_MASK) + +/* ASRC Resampling Ratio High (RRH) */ +#define EASRC_RRH_RS_VLD_SHIFT 31 +#define EASRC_RRH_RS_VLD_MASK BIT(EASRC_RRH_RS_VLD_SHIFT) +#define EASRC_RRH_RS_VLD BIT(EASRC_RRH_RS_VLD_SHIFT) +#define EASRC_RRH_RS_RH_SHIFT 0 +#define EASRC_RRH_RS_RH_WIDTH 12 +#define EASRC_RRH_RS_RH_MASK ((BIT(EASRC_RRH_RS_RH_WIDTH) - 1) \ + << EASRC_RRH_RS_RH_SHIFT) +#define EASRC_RRH_RS_RH(v) (((v) << EASRC_RRH_RS_RH_SHIFT) \ + & EASRC_RRH_RS_RH_MASK) + +/* ASRC Resampling Ratio Update Control (RSUC) */ +#define EASRC_RSUC_RS_RM_SHIFT 0 +#define EASRC_RSUC_RS_RM_WIDTH 32 +#define EASRC_RSUC_RS_RM_MASK ((BIT(EASRC_RSUC_RS_RM_WIDTH) - 1) \ + << EASRC_RSUC_RS_RM_SHIFT) +#define EASRC_RSUC_RS_RM(v) (((v) << EASRC_RSUC_RS_RM_SHIFT) \ + & EASRC_RSUC_RS_RM_MASK) + +/* ASRC Resampling Ratio Update Rate (RRUR) */ +#define EASRC_RRUR_RRR_SHIFT 0 +#define EASRC_RRUR_RRR_WIDTH 31 +#define EASRC_RRUR_RRR_MASK ((BIT(EASRC_RRUR_RRR_WIDTH) - 1) \ + << EASRC_RRUR_RRR_SHIFT) +#define EASRC_RRUR_RRR(v) (((v) << EASRC_RRUR_RRR_SHIFT) \ + & EASRC_RRUR_RRR_MASK) + +/* ASRC Resampling Center Tap Coefficient Low (RCTCL) */ +#define EASRC_RCTCL_RS_CL_SHIFT 0 +#define EASRC_RCTCL_RS_CL_WIDTH 32 +#define EASRC_RCTCL_RS_CL_MASK ((BIT(EASRC_RCTCL_RS_CL_WIDTH) - 1) \ + << EASRC_RCTCL_RS_CL_SHIFT) +#define EASRC_RCTCL_RS_CL(v) (((v) << EASRC_RCTCL_RS_CL_SHIFT) \ + & EASRC_RCTCL_RS_CL_MASK) + +/* ASRC Resampling Center Tap Coefficient High (RCTCH) */ +#define EASRC_RCTCH_RS_CH_SHIFT 0 +#define EASRC_RCTCH_RS_CH_WIDTH 32 +#define EASRC_RCTCH_RS_CH_MASK ((BIT(EASRC_RCTCH_RS_CH_WIDTH) - 1) \ + << EASRC_RCTCH_RS_CH_SHIFT) +#define EASRC_RCTCH_RS_CH(v) (((v) << EASRC_RCTCH_RS_CH_SHIFT) \ + & EASRC_RCTCH_RS_CH_MASK) + +/* ASRC Prefilter Coefficient FIFO (PCF) */ +#define EASRC_PCF_CD_SHIFT 0 +#define EASRC_PCF_CD_WIDTH 32 +#define EASRC_PCF_CD_MASK ((BIT(EASRC_PCF_CD_WIDTH) - 1) \ + << EASRC_PCF_CD_SHIFT) +#define EASRC_PCF_CD(v) (((v) << EASRC_PCF_CD_SHIFT) \ + & EASRC_PCF_CD_MASK) + +/* ASRC Context Resampling Coefficient Memory (CRCM) */ +#define EASRC_CRCM_RS_CWD_SHIFT 0 +#define EASRC_CRCM_RS_CWD_WIDTH 32 +#define EASRC_CRCM_RS_CWD_MASK ((BIT(EASRC_CRCM_RS_CWD_WIDTH) - 1) \ + << EASRC_CRCM_RS_CWD_SHIFT) +#define EASRC_CRCM_RS_CWD(v) (((v) << EASRC_CRCM_RS_CWD_SHIFT) \ + & EASRC_CRCM_RS_CWD_MASK) + +/* ASRC Context Resampling Coefficient Control (CRCC) */ +#define EASRC_CRCC_RS_CA_SHIFT 16 +#define EASRC_CRCC_RS_CA_WIDTH 11 +#define EASRC_CRCC_RS_CA_MASK ((BIT(EASRC_CRCC_RS_CA_WIDTH) - 1) \ + << EASRC_CRCC_RS_CA_SHIFT) +#define EASRC_CRCC_RS_CA(v) (((v) << EASRC_CRCC_RS_CA_SHIFT) \ + & EASRC_CRCC_RS_CA_MASK) +#define EASRC_CRCC_RS_TAPS_SHIFT 1 +#define EASRC_CRCC_RS_TAPS_WIDTH 2 +#define EASRC_CRCC_RS_TAPS_MASK ((BIT(EASRC_CRCC_RS_TAPS_WIDTH) - 1) \ + << EASRC_CRCC_RS_TAPS_SHIFT) +#define EASRC_CRCC_RS_TAPS(v) (((v) << EASRC_CRCC_RS_TAPS_SHIFT) \ + & EASRC_CRCC_RS_TAPS_MASK) +#define EASRC_CRCC_RS_CPR_SHIFT 0 +#define EASRC_CRCC_RS_CPR_MASK BIT(EASRC_CRCC_RS_CPR_SHIFT) +#define EASRC_CRCC_RS_CPR BIT(EASRC_CRCC_RS_CPR_SHIFT) + +/* ASRC Interrupt_Control (IC) */ +#define EASRC_IRQC_RSDM_SHIFT 8 +#define EASRC_IRQC_RSDM_WIDTH 4 +#define EASRC_IRQC_RSDM_MASK ((BIT(EASRC_IRQC_RSDM_WIDTH) - 1) \ + << EASRC_IRQC_RSDM_SHIFT) +#define EASRC_IRQC_RSDM(v) (((v) << EASRC_IRQC_RSDM_SHIFT) \ + & EASRC_IRQC_RSDM_MASK) +#define EASRC_IRQC_OERM_SHIFT 4 +#define EASRC_IRQC_OERM_WIDTH 4 +#define EASRC_IRQC_OERM_MASK ((BIT(EASRC_IRQC_OERM_WIDTH) - 1) \ + << EASRC_IRQC_OERM_SHIFT) +#define EASRC_IRQC_OERM(v) (((v) << EASRC_IRQC_OERM_SHIFT) \ + & EASRC_IEQC_OERM_MASK) +#define EASRC_IRQC_IOM_SHIFT 0 +#define EASRC_IRQC_IOM_WIDTH 4 +#define EASRC_IRQC_IOM_MASK ((BIT(EASRC_IRQC_IOM_WIDTH) - 1) \ + << EASRC_IRQC_IOM_SHIFT) +#define EASRC_IRQC_IOM(v) (((v) << EASRC_IRQC_IOM_SHIFT) \ + & EASRC_IRQC_IOM_MASK) + +/* ASRC Interrupt Status Flags (ISF) */ +#define EASRC_IRQF_RSD_SHIFT 8 +#define EASRC_IRQF_RSD_WIDTH 4 +#define EASRC_IRQF_RSD_MASK ((BIT(EASRC_IRQF_RSD_WIDTH) - 1) \ + << EASRC_IRQF_RSD_SHIFT) +#define EASRC_IRQF_RSD(v) (((v) << EASRC_IRQF_RSD_SHIFT) \ + & EASRC_IRQF_RSD_MASK) +#define EASRC_IRQF_OER_SHIFT 4 +#define EASRC_IRQF_OER_WIDTH 4 +#define EASRC_IRQF_OER_MASK ((BIT(EASRC_IRQF_OER_WIDTH) - 1) \ + << EASRC_IRQF_OER_SHIFT) +#define EASRC_IRQF_OER(v) (((v) << EASRC_IRQF_OER_SHIFT) \ + & EASRC_IRQF_OER_MASK) +#define EASRC_IRQF_IFO_SHIFT 0 +#define EASRC_IRQF_IFO_WIDTH 4 +#define EASRC_IRQF_IFO_MASK ((BIT(EASRC_IRQF_IFO_WIDTH) - 1) \ + << EASRC_IRQF_IFO_SHIFT) +#define EASRC_IRQF_IFO(v) (((v) << EASRC_IRQF_IFO_SHIFT) \ + & EASRC_IRQF_IFO_MASK) + +/* ASRC Context Channel STAT */ +#define EASRC_CSx_CSx_SHIFT 0 +#define EASRC_CSx_CSx_WIDTH 32 +#define EASRC_CSx_CSx_MASK ((BIT(EASRC_CSx_CSx_WIDTH) - 1) \ + << EASRC_CSx_CSx_SHIFT) +#define EASRC_CSx_CSx(v) (((v) << EASRC_CSx_CSx_SHIFT) \ + & EASRC_CSx_CSx_MASK) + +/* ASRC Debug Control Register */ +#define EASRC_DBGC_DMS_SHIFT 0 +#define EASRC_DBGC_DMS_WIDTH 6 +#define EASRC_DBGC_DMS_MASK ((BIT(EASRC_DBGC_DMS_WIDTH) - 1) \ + << EASRC_DBGC_DMS_SHIFT) +#define EASRC_DBGC_DMS(v) (((v) << EASRC_DBGC_DMS_SHIFT) \ + & EASRC_DBGC_DMS_MASK) + +/* ASRC Debug Status Register */ +#define EASRC_DBGS_DS_SHIFT 0 +#define EASRC_DBGS_DS_WIDTH 32 +#define EASRC_DBGS_DS_MASK ((BIT(EASRC_DBGS_DS_WIDTH) - 1) \ + << EASRC_DBGS_DS_SHIFT) +#define EASRC_DBGS_DS(v) (((v) << EASRC_DBGS_DS_SHIFT) \ + & EASRC_DBGS_DS_MASK) + +/* General Constants */ +#define EASRC_CTX_MAX_NUM 4 +#define EASRC_32b_MASK (BIT(32) - 1) +#define EASRC_64b_MASK (BIT(64) - 1) +#define EASRC_RS_COEFF_MEM 0 +#define EASRC_PF_COEFF_MEM 1 + +/* Prefilter constants */ +#define EASRC_PF_ST1_ONLY 0 +#define EASRC_PF_TWO_STAGE_MODE 1 +#define EASRC_PF_ST1_COEFF_WR 0 +#define EASRC_PF_ST2_COEFF_WR 1 +#define EASRC_MAX_PF_TAPS 384 + +/* Resampling constants */ +#define EASRC_RS_32_TAPS 0 +#define EASRC_RS_64_TAPS 1 +#define EASRC_RS_128_TAPS 2 + +/* Initialization mode */ +#define EASRC_INIT_MODE_SW_CONTROL 0 +#define EASRC_INIT_MODE_REPLICATE 1 +#define EASRC_INIT_MODE_ZERO_FILL 2 + +/* directions */ +#define IN 0 +#define OUT 1 + +/* FIFO watermarks */ +#define FSL_EASRC_INPUTFIFO_WML 0x4 +#define FSL_EASRC_OUTPUTFIFO_WML 0x1 + +#define EASRC_INPUTFIFO_THRESHOLD_MIN 0 +#define EASRC_INPUTFIFO_THRESHOLD_MAX 127 +#define EASRC_OUTPUTFIFO_THRESHOLD_MIN 0 +#define EASRC_OUTPUTFIFO_THRESHOLD_MAX 63 + +#define EASRC_DMA_BUFFER_SIZE (1024 * 48 * 9) +#define EASRC_MAX_BUFFER_SIZE (1024 * 48) + +#define FIRMWARE_MAGIC 0xDEAD +#define FIRMWARE_VERSION 1 + +enum easrc_word_width { + EASRC_WIDTH_16_BIT = 0, + EASRC_WIDTH_20_BIT = 1, + EASRC_WIDTH_24_BIT = 2, + EASRC_WIDTH_32_BIT = 3, +}; + +struct __attribute__((__packed__)) asrc_firmware_hdr { + u32 magic; + u32 interp_scen; + u32 prefil_scen; + u32 firmware_version; +}; + +struct __attribute__((__packed__)) interp_params { + u32 magic; + u32 num_taps; + u32 num_phases; + u64 center_tap; + u64 coeff[8192]; +}; + +struct __attribute__((__packed__)) prefil_params { + u32 magic; + u32 insr; + u32 outsr; + u32 st1_taps; + u32 st2_taps; + u32 st1_exp; + u64 coeff[256]; +}; + +struct dma_block { + void *dma_vaddr; + unsigned int length; + unsigned int max_buf_size; +}; + +struct fsl_easrc_data_fmt { + unsigned int width : 2; + unsigned int endianness : 1; + unsigned int unsign : 1; + unsigned int floating_point : 1; + unsigned int iec958: 1; + unsigned int sample_pos: 5; + unsigned int addexp; +}; + +struct fsl_easrc_io_params { + struct fsl_easrc_data_fmt fmt; + unsigned int group_len; + unsigned int iterations; + unsigned int access_len; + unsigned int fifo_wtmk; + unsigned int sample_rate; + unsigned int sample_format; + unsigned int norm_rate; +}; + +struct fsl_easrc_slot { + bool busy; + int ctx_index; + int num_channel; /*maximum is 8*/ + int min_channel; + int max_channel; + int pf_mem_used; +}; + +struct fsl_easrc_context { + enum asrc_pair_index index; + struct fsl_easrc *easrc; + struct dma_chan *dma_chan[2]; + struct dma_async_tx_descriptor *desc[2]; + struct fsl_easrc_io_params in_params; + struct fsl_easrc_io_params out_params; + struct imx_dma_data dma_data; + unsigned int channels; + unsigned int st1_num_taps; + unsigned int st2_num_taps; + unsigned int st1_num_exp; + unsigned int pf_init_mode; + unsigned int rs_init_mode; + unsigned int ctx_streams; + unsigned int pos; + u64 rs_ratio; + u64 *st1_coeff; + u64 *st2_coeff; + int in_filled_sample; + int out_missed_sample; + int st1_addexp; + int st2_addexp; + void *private_data; +}; + +/** + * fsl_easrc: EASRC private data + * + * name : name of EASRC device + * @pdev: platform device pointer + * @regmap: regmap handler + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @ctx: context pointer + * @slot: slot setting + * @mem_clk: clock source to access register + * @firmware_hdr: the header of firmware + * @interp: pointer to interpolation filter coeff + * @prefil: pointer to prefilter coeff + * @fw: firmware of coeff table + * @fw_name: firmware name + * @paddr: physical address to the base address of registers + * @rs_num_taps: resample filter taps, 32, 64, or 128 + * @bps_i2c958: bits per sample of iec958 + * @chn_avail: available channels, maximum 32 + * @lock: spin lock for resource protection + * @easrc_rate: default sample rate for ASoC Back-Ends + * @easrc_format: default sample format for ASoC Back-Ends + */ + +struct fsl_easrc { + char name[32]; + struct platform_device *pdev; + struct regmap *regmap; + struct miscdevice easrc_miscdev; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct fsl_easrc_context *ctx[EASRC_CTX_MAX_NUM]; + struct fsl_easrc_slot slot[EASRC_CTX_MAX_NUM][2]; + struct clk *mem_clk; + struct asrc_firmware_hdr *firmware_hdr; + struct interp_params *interp; + struct prefil_params *prefil; + const struct firmware *fw; + const char *fw_name; + unsigned long paddr; + unsigned int rs_num_taps; + unsigned int bps_iec958[EASRC_CTX_MAX_NUM]; + unsigned int chn_avail; + u64 *rs_coeff; + u64 const_coeff; + int firmware_loaded; + spinlock_t lock; /* spin lock for resource protection */ + int easrc_rate; + int easrc_format; +}; + +struct dma_chan *fsl_easrc_get_dma_channel( + struct fsl_easrc_context *ctx, bool dir); +int fsl_easrc_request_context( + struct fsl_easrc_context *ctx, unsigned int channels); +int fsl_easrc_release_context(struct fsl_easrc_context *ctx); + +#define DRV_NAME "fsl-easrc-dma" +#endif /* _FSL_EASRC_H */ diff --git a/sound/soc/fsl/fsl_easrc_dma.c b/sound/soc/fsl/fsl_easrc_dma.c new file mode 100644 index 000000000000..eb0ed164ef58 --- /dev/null +++ b/sound/soc/fsl/fsl_easrc_dma.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/platform_data/dma-imx.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_easrc.h" + +#define FSL_ASRC_DMABUF_SIZE (256 * 1024) + +static struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65532, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool filter(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +static void fsl_easrc_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + + ctx->pos += snd_pcm_lib_period_bytes(substream); + if (ctx->pos >= snd_pcm_lib_buffer_bytes(substream)) + ctx->pos = 0; + + snd_pcm_period_elapsed(substream); +} + +static int fsl_easrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) +{ + u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + unsigned long flags = DMA_CTRL_ACK; + + /* Prepare and submit Front-End DMA channel */ + if (!substream->runtime->no_period_wakeup) + flags |= DMA_PREP_INTERRUPT; + + ctx->pos = 0; + ctx->desc[!dir] = dmaengine_prep_dma_cyclic( + ctx->dma_chan[!dir], runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, flags); + if (!ctx->desc[!dir]) { + dev_err(dev, "failed to prepare slave DMA for Front-End\n"); + return -ENOMEM; + } + + ctx->desc[!dir]->callback = fsl_easrc_dma_complete; + ctx->desc[!dir]->callback_param = substream; + + dmaengine_submit(ctx->desc[!dir]); + + /* Prepare and submit Back-End DMA channel */ + ctx->desc[dir] = dmaengine_prep_dma_cyclic( + ctx->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0); + if (!ctx->desc[dir]) { + dev_err(dev, "failed to prepare slave DMA for Back-End\n"); + return -ENOMEM; + } + + dmaengine_submit(ctx->desc[dir]); + + return 0; +} + +static int fsl_easrc_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_easrc_dma_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(ctx->dma_chan[IN]); + dma_async_issue_pending(ctx->dma_chan[OUT]); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(ctx->dma_chan[OUT]); + dmaengine_terminate_all(ctx->dma_chan[IN]); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_easrc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL; + struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + struct fsl_easrc *easrc = ctx->easrc; + struct dma_slave_config config_fe, config_be; + enum asrc_pair_index index = ctx->index; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + int stream = substream->stream; + struct imx_dma_data *tmp_data; + struct snd_soc_dpcm *dpcm; + struct dma_chan *tmp_chan; + struct device *dev_be; + u8 dir = tx ? OUT : IN; + dma_cap_mask_t mask; + enum sdma_peripheral_type be_peripheral_type; + struct device_node *of_dma_node; + int ret; + + /* Fetch the Back-End dma_data from DPCM */ + list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *substream_be; + struct snd_soc_dai *dai = be->cpu_dai; + + if (dpcm->fe != rtd) + continue; + + substream_be = snd_soc_dpcm_get_substream(be, stream); + dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be); + dev_be = dai->dev; + break; + } + + if (!dma_params_be) { + dev_err(dev, "failed to get the substream of Back-End\n"); + return -EINVAL; + } + + /* Override dma_data of the Front-End and config its dmaengine */ + dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + dma_params_fe->addr = easrc->paddr + REG_EASRC_FIFO(!dir, index); + dma_params_fe->maxburst = dma_params_be->maxburst; + + ctx->dma_chan[!dir] = fsl_easrc_get_dma_channel(ctx, !dir); + if (!ctx->dma_chan[!dir]) { + dev_err(dev, "failed to request DMA channel\n"); + return -EINVAL; + } + + memset(&config_fe, 0, sizeof(config_fe)); + ret = snd_dmaengine_pcm_prepare_slave_config(substream, + params, &config_fe); + if (ret) { + dev_err(dev, "failed to prepare DMA config for Front-End\n"); + return ret; + } + + ret = dmaengine_slave_config(ctx->dma_chan[!dir], &config_fe); + if (ret) { + dev_err(dev, "failed to config DMA channel for Front-End\n"); + return ret; + } + + of_dma_node = ctx->dma_chan[!dir]->device->dev->of_node; + /* Request and config DMA channel for Back-End */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_CYCLIC, mask); + + /* Get DMA request of Back-End */ + tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + ctx->dma_data.dma_request = tmp_data->dma_request; + be_peripheral_type = tmp_data->peripheral_type; + if (tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + ctx->dma_data.dst_dualfifo = true; + if (!tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL) + ctx->dma_data.src_dualfifo = true; + } + dma_release_channel(tmp_chan); + } + + /* Get DMA request of Front-End */ + tmp_chan = fsl_easrc_get_dma_channel(ctx, dir); + if (tmp_chan) { + tmp_data = tmp_chan->private; + if (tmp_data) { + ctx->dma_data.dma_request2 = tmp_data->dma_request; + ctx->dma_data.peripheral_type = + tmp_data->peripheral_type; + ctx->dma_data.priority = tmp_data->priority; + } + dma_release_channel(tmp_chan); + } + + /* For sdma DEV_TO_DEV, there is two dma request + * But for emda DEV_TO_DEV, there is only one dma request, which is + * from the BE. + */ + if (ctx->dma_data.dma_request2 != ctx->dma_data.dma_request) + ctx->dma_chan[dir] = + __dma_request_channel(&mask, filter, &ctx->dma_data, + of_dma_node); + else + ctx->dma_chan[dir] = + dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); + + if (!ctx->dma_chan[dir]) { + dev_err(dev, "failed to request DMA channel for Back-End\n"); + return -EINVAL; + } + + if (easrc->easrc_format == SNDRV_PCM_FORMAT_S16_LE) + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + + memset(&config_be, 0, sizeof(config_be)); + + config_be.direction = DMA_DEV_TO_DEV; + config_be.src_addr_width = buswidth; + config_be.src_maxburst = dma_params_be->maxburst; + config_be.dst_addr_width = buswidth; + config_be.dst_maxburst = dma_params_be->maxburst; + + if (tx) { + config_be.src_addr = easrc->paddr + REG_EASRC_RDFIFO(index); + config_be.dst_addr = dma_params_be->addr; + } else { + config_be.dst_addr = easrc->paddr + REG_EASRC_WRFIFO(index); + config_be.src_addr = dma_params_be->addr; + } + + ret = dmaengine_slave_config(ctx->dma_chan[dir], &config_be); + if (ret) { + dev_err(dev, "failed to config DMA channel for Back-End\n"); + return ret; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int fsl_easrc_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, NULL); + + if (ctx->dma_chan[IN]) + dma_release_channel(ctx->dma_chan[IN]); + + if (ctx->dma_chan[OUT]) + dma_release_channel(ctx->dma_chan[OUT]); + + ctx->dma_chan[IN] = NULL; + ctx->dma_chan[OUT] = NULL; + + return 0; +} + +static int fsl_easrc_dma_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct fsl_easrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_context *ctx; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 dir = tx ? OUT : IN; + struct dma_slave_caps dma_caps; + struct dma_chan *tmp_chan; + struct snd_dmaengine_dai_dma_data *dma_data; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->easrc = easrc; + + runtime->private_data = ctx; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "failed to set pcm hw params periods\n"); + return ret; + } + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + fsl_easrc_request_context(ctx, 1); + + tmp_chan = fsl_easrc_get_dma_channel(ctx, dir); + if (!tmp_chan) { + dev_err(dev, "can't get dma channel\n"); + return -EINVAL; + } + + ret = dma_get_slave_caps(tmp_chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + snd_imx_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + snd_imx_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + snd_imx_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + if (tmp_chan) + dma_release_channel(tmp_chan); + + fsl_easrc_release_context(ctx); + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + + return 0; +} + +static int fsl_easrc_dma_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + struct fsl_easrc *easrc; + + if (!ctx) + return 0; + + easrc = ctx->easrc; + + if (easrc->ctx[ctx->index] == ctx) + easrc->ctx[ctx->index] = NULL; + + kfree(ctx); + + return 0; +} + +static snd_pcm_uframes_t fsl_easrc_dma_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_easrc_context *ctx = runtime->private_data; + + return bytes_to_frames(substream->runtime, ctx->pos); +} + +static const struct snd_pcm_ops fsl_easrc_dma_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_easrc_dma_hw_params, + .hw_free = fsl_easrc_dma_hw_free, + .trigger = fsl_easrc_dma_trigger, + .open = fsl_easrc_dma_startup, + .close = fsl_easrc_dma_shutdown, + .pointer = fsl_easrc_dma_pcm_pointer, +}; + +static int fsl_easrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + int ret, i; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(card->dev, "failed to set DMA mask\n"); + return ret; + } + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + FSL_ASRC_DMABUF_SIZE, + &substream->dma_buffer); + if (ret) { + dev_err(card->dev, "failed to allocate DMA buffer\n"); + goto err; + } + } + + return 0; + +err: + if (--i == 0 && pcm->streams[i].substream) + snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer); + + return ret; +} + +static void fsl_easrc_dma_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } +} + +struct snd_soc_component_driver fsl_easrc_dma_component = { + .name = DRV_NAME, + .ops = &fsl_easrc_dma_pcm_ops, + .pcm_new = fsl_easrc_dma_pcm_new, + .pcm_free = fsl_easrc_dma_pcm_free, +}; +EXPORT_SYMBOL_GPL(fsl_easrc_dma_component); diff --git a/sound/soc/fsl/fsl_easrc_m2m.c b/sound/soc/fsl/fsl_easrc_m2m.c new file mode 100644 index 000000000000..86c6a46eba2b --- /dev/null +++ b/sound/soc/fsl/fsl_easrc_m2m.c @@ -0,0 +1,970 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +struct fsl_easrc_m2m { + struct fsl_easrc *easrc; + struct fsl_easrc_context *ctx; + struct completion complete[2]; + struct dma_block dma_block[2]; + unsigned int ctx_hold; + unsigned int easrc_active; + unsigned int first_convert; + unsigned int sg_nodes[2]; + unsigned int in_filled_len; + struct scatterlist sg[2][9]; + struct dma_async_tx_descriptor *desc[2]; + spinlock_t lock; /* protect mem resource */ +}; + +void fsl_easrc_get_status(struct fsl_easrc_context *ctx, + struct asrc_status_flags *flags) +{ + flags->overload_error = 0; +} + +#define mxc_easrc_dma_umap_in(dev, m2m) \ + dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \ + DMA_MEM_TO_DEV) \ + +#define mxc_easrc_dma_umap_out(dev, m2m) \ + dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \ + DMA_DEV_TO_MEM) \ + +#define EASRC_xPUT_DMA_CALLBACK(dir) \ + ((dir == IN) ? fsl_easrc_input_dma_callback \ + : fsl_easrc_output_dma_callback) + +#define DIR_STR(dir) dir == IN ? "in" : "out" + +static void fsl_easrc_input_dma_callback(void *data) +{ + struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data; + + complete(&m2m->complete[IN]); +} + +static void fsl_easrc_output_dma_callback(void *data) +{ + struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data; + + complete(&m2m->complete[OUT]); +} + +static int fsl_allocate_dma_buf(struct fsl_easrc_m2m *m2m) +{ + struct dma_block *input = &m2m->dma_block[IN]; + struct dma_block *output = &m2m->dma_block[OUT]; + + input->dma_vaddr = kzalloc(input->length, GFP_KERNEL); + if (!input->dma_vaddr) + return -ENOMEM; + + output->dma_vaddr = kzalloc(output->length, GFP_KERNEL); + if (!output->dma_vaddr) + goto alloc_fail; + + return 0; + +alloc_fail: + kfree(input->dma_vaddr); + + return -ENOMEM; +} + +static int fsl_easrc_dmaconfig(struct fsl_easrc_m2m *m2m, + struct dma_chan *chan, + u32 dma_addr, void *buf_addr, u32 buf_len, + bool dir, int bits) +{ + struct dma_async_tx_descriptor *desc = m2m->desc[dir]; + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + struct device *dev = &easrc->pdev->dev; + unsigned int sg_nent = m2m->sg_nodes[dir]; + struct scatterlist *sg = m2m->sg[dir]; + struct dma_slave_config slave_config; + enum dma_slave_buswidth buswidth; + int ret, i; + + switch (bits) { + case 16: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 24: + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; + break; + default: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + } + + if (dir == IN) { + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.dst_addr = dma_addr; + slave_config.dst_addr_width = buswidth; + slave_config.dst_maxburst = + ctx->in_params.fifo_wtmk * ctx->channels; + } else { + slave_config.direction = DMA_DEV_TO_MEM; + slave_config.src_addr = dma_addr; + slave_config.src_addr_width = buswidth; + slave_config.src_maxburst = + ctx->out_params.fifo_wtmk * ctx->channels; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) { + dev_err(dev, "failed to config dmaengine for %sput task: %d\n", + DIR_STR(dir), ret); + return -EINVAL; + } + + sg_init_table(sg, sg_nent); + switch (sg_nent) { + case 1: + sg_init_one(sg, buf_addr, buf_len); + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + for (i = 0; i < (sg_nent - 1); i++) + sg_set_buf(&sg[i], + buf_addr + i * m2m->dma_block[dir].max_buf_size, + m2m->dma_block[dir].max_buf_size); + + sg_set_buf(&sg[i], + buf_addr + i * m2m->dma_block[dir].max_buf_size, + buf_len - i * m2m->dma_block[dir].max_buf_size); + break; + default: + dev_err(dev, "invalid input DMA nodes number: %d\n", sg_nent); + return -EINVAL; + } + + ret = dma_map_sg(dev, sg, sg_nent, slave_config.direction); + if (ret != sg_nent) { + dev_err(dev, "failed to map DMA sg for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, + slave_config.direction, + DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(dev, "failed to prepare dmaengine for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + m2m->desc[dir] = desc; + m2m->desc[dir]->callback = EASRC_xPUT_DMA_CALLBACK(dir); + + desc->callback = EASRC_xPUT_DMA_CALLBACK(dir); + desc->callback_param = m2m; + + return 0; +} + +static long fsl_easrc_calc_outbuf_len(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_easrc_context *ctx = m2m->ctx; + unsigned int out_length; + unsigned int in_width, out_width; + unsigned int channels = ctx->channels; + unsigned int in_samples, out_samples; + + in_width = snd_pcm_format_physical_width(ctx->in_params.sample_format) / 8; + out_width = snd_pcm_format_physical_width(ctx->out_params.sample_format) / 8; + + m2m->in_filled_len += pbuf->input_buffer_length; + if (m2m->in_filled_len <= ctx->in_filled_sample * in_width * channels) { + out_length = 0; + } else { + in_samples = m2m->in_filled_len / (in_width * channels) - ctx->in_filled_sample; + out_samples = ctx->out_params.sample_rate * in_samples / + ctx->in_params.sample_rate; + out_length = out_samples * out_width * channels; + m2m->in_filled_len = ctx->in_filled_sample * in_width * channels; + } + + return out_length; +} + +static long fsl_easrc_prepare_io_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf, + bool dir) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = m2m->ctx; + struct dma_chan *dma_chan = ctx->dma_chan[dir]; + unsigned int *dma_len = &m2m->dma_block[dir].length; + unsigned int *sg_nodes = &m2m->sg_nodes[dir]; + void *dma_vaddr = m2m->dma_block[dir].dma_vaddr; + enum asrc_pair_index index = m2m->ctx->index; + unsigned int buf_len, bits; + u32 fifo_addr; + void __user *buf_vaddr; + + if (dir == IN) { + buf_vaddr = (void __user *)buf->input_buffer_vaddr; + buf_len = buf->input_buffer_length; + bits = snd_pcm_format_physical_width(ctx->in_params.sample_format); + fifo_addr = easrc->paddr + REG_EASRC_WRFIFO(index); + } else { + buf_vaddr = (void __user *)buf->output_buffer_vaddr; + buf_len = buf->output_buffer_length; + bits = snd_pcm_format_physical_width(ctx->out_params.sample_format); + fifo_addr = easrc->paddr + REG_EASRC_RDFIFO(index); + } + + if (buf_len > EASRC_DMA_BUFFER_SIZE || + (dir == IN && (buf_len % (bits / 8)))) { + dev_err(dev, "%sput buffer size is error: [%d]\n", + DIR_STR(dir), buf_len); + return -EINVAL; + } + + if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) + return -EFAULT; + + *dma_len = buf_len; + + if (dir == OUT) + *dma_len = fsl_easrc_calc_outbuf_len(m2m, buf); + + if (*dma_len <= 0) + return 0; + + *sg_nodes = *dma_len / m2m->dma_block[dir].max_buf_size; + if (*dma_len % m2m->dma_block[dir].max_buf_size) + *sg_nodes += 1; + + return fsl_easrc_dmaconfig(m2m, dma_chan, fifo_addr, dma_vaddr, + *dma_len, dir, bits); +} + +static long fsl_easrc_prepare_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + int ret; + + ret = fsl_easrc_prepare_io_buffer(m2m, buf, IN); + if (ret) { + dev_err(dev, "failed to prepare input buffer %d\n", ret); + return ret; + } + + ret = fsl_easrc_prepare_io_buffer(m2m, buf, OUT); + if (ret) { + dev_err(dev, "failed to prepare output buffer %d\n", ret); + return ret; + } + + return 0; +} + +int fsl_easrc_process_buffer_pre(struct fsl_easrc_m2m *m2m, bool dir) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + + if (!wait_for_completion_interruptible_timeout(&m2m->complete[dir], + 10 * HZ)) { + dev_err(dev, "%sput DMA task timeout\n", DIR_STR(dir)); + return -ETIME; + } else if (signal_pending(current)) { + dev_err(dev, "%sput task forcibly aborted\n", DIR_STR(dir)); + return -ERESTARTSYS; + } + + return 0; +} + +static unsigned int fsl_easrc_get_output_FIFO_size(struct fsl_easrc_m2m *m2m) +{ + struct fsl_easrc *easrc = m2m->easrc; + enum asrc_pair_index index = m2m->ctx->index; + u32 val; + + regmap_read(easrc->regmap, REG_EASRC_SFS(index), &val); + + val &= EASRC_SFS_NSGO_MASK; + + return val >> EASRC_SFS_NSGO_SHIFT; +} + +static void fsl_easrc_read_last_FIFO(struct fsl_easrc_m2m *m2m) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct dma_block *output = &m2m->dma_block[OUT]; + struct fsl_easrc_context *ctx = m2m->ctx; + enum asrc_pair_index index = m2m->ctx->index; + u32 i, reg, size, t_size = 0, width; + u32 *reg32 = NULL; + u16 *reg16 = NULL; + u8 *reg24 = NULL; + + width = snd_pcm_format_physical_width(ctx->out_params.sample_format); + + if (width == 32) + reg32 = output->dma_vaddr + output->length; + else if (width == 16) + reg16 = output->dma_vaddr + output->length; + else + reg24 = output->dma_vaddr + output->length; +retry: + size = fsl_easrc_get_output_FIFO_size(m2m); + for (i = 0; i < size * ctx->channels; i++) { + regmap_read(easrc->regmap, REG_EASRC_RDFIFO(index), ®); + + if (reg32) { + *(reg32) = reg; + reg32++; + } else if (reg16) { + *(reg16) = (u16)reg; + reg16++; + } else { + *reg24++ = (u8)reg; + *reg24++ = (u8)(reg >> 8); + *reg24++ = (u8)(reg >> 16); + } + } + t_size += size; + + if (size) + goto retry; + + if (reg32) + output->length += t_size * ctx->channels * 4; + else if (reg16) + output->length += t_size * ctx->channels * 2; + else + output->length += t_size * ctx->channels * 3; +} + +static long fsl_easrc_process_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + unsigned long lock_flags; + int ret; + + /* Check input task first */ + ret = fsl_easrc_process_buffer_pre(m2m, IN); + if (ret) { + mxc_easrc_dma_umap_in(dev, m2m); + if (m2m->dma_block[OUT].length > 0) + mxc_easrc_dma_umap_out(dev, m2m); + return ret; + } + + /* ...then output task*/ + if (m2m->dma_block[OUT].length > 0) { + ret = fsl_easrc_process_buffer_pre(m2m, OUT); + if (ret) { + mxc_easrc_dma_umap_in(dev, m2m); + mxc_easrc_dma_umap_out(dev, m2m); + return ret; + } + } + + mxc_easrc_dma_umap_in(dev, m2m); + if (m2m->dma_block[OUT].length > 0) + mxc_easrc_dma_umap_out(dev, m2m); + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (!m2m->ctx_hold) { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + return -EFAULT; + } + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + /* Fetch the remaining data */ + fsl_easrc_read_last_FIFO(m2m); + + /* Update final lengths after getting last FIFO */ + buf->input_buffer_length = m2m->dma_block[IN].length; + buf->output_buffer_length = m2m->dma_block[OUT].length; + + if (copy_to_user((void __user *)buf->output_buffer_vaddr, + m2m->dma_block[OUT].dma_vaddr, + m2m->dma_block[OUT].length)) + return -EFAULT; + + return 0; +} + +void fsl_easrc_submit_dma(struct fsl_easrc_m2m *m2m) +{ + /* Submit DMA request */ + dmaengine_submit(m2m->desc[IN]); + dma_async_issue_pending(m2m->desc[IN]->chan); + + if (m2m->dma_block[OUT].length > 0) { + dmaengine_submit(m2m->desc[OUT]); + dma_async_issue_pending(m2m->desc[OUT]->chan); + } +} + +static long fsl_easrc_ioctl_req_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct asrc_req req; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&req, user, sizeof(req)); + if (ret) { + dev_err(dev, "failed to get req from user space:%ld\n", ret); + return ret; + } + + ret = fsl_easrc_request_context(m2m->ctx, req.chn_num); + if (ret < 0) { + dev_err(dev, "failed to request context:%ld\n", ret); + return ret; + } + + /* request context returns the context id in case of success */ + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->ctx_hold = 1; + req.index = m2m->ctx->index; + req.supported_in_format = FSL_EASRC_FORMATS; + req.supported_out_format = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + ret = copy_to_user(user, &req, sizeof(req)); + if (ret) { + dev_err(dev, "failed to send req to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_config_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + enum asrc_pair_index index = m2m->ctx->index; + struct device *dev = &easrc->pdev->dev; + struct asrc_config config; + int ret; + int in_word_size, out_word_size; + + ret = copy_from_user(&config, user, sizeof(config)); + if (ret) { + dev_err(dev, "failed to get config from user space: %d\n", ret); + return ret; + } + + /* set context configuration parameters received from userspace */ + ctx->in_params.sample_rate = config.input_sample_rate; + ctx->out_params.sample_rate = config.output_sample_rate; + + ctx->in_params.fifo_wtmk = FSL_EASRC_INPUTFIFO_WML; + ctx->out_params.fifo_wtmk = FSL_EASRC_OUTPUTFIFO_WML; + + ctx->in_params.sample_format = config.input_format; + ctx->out_params.sample_format = config.output_format; + + ctx->channels = config.channel_num; + ctx->rs_init_mode = 0x2; + ctx->pf_init_mode = 0x2; + + ret = fsl_easrc_set_ctx_format(ctx, + &ctx->in_params.sample_format, + &ctx->out_params.sample_format); + if (ret) + return ret; + + ret = fsl_easrc_config_context(easrc, index); + if (ret) { + dev_err(dev, "failed to config context %d\n", ret); + return ret; + } + + ctx->in_params.iterations = 1; + ctx->in_params.group_len = ctx->channels; + ctx->in_params.access_len = ctx->channels; + ctx->out_params.iterations = 1; + ctx->out_params.group_len = ctx->channels; + ctx->out_params.access_len = ctx->channels; + + /* You can also call fsl_easrc_set_ctx_organziation for + * sample interleaving support + */ + ret = fsl_easrc_set_ctx_organziation(ctx); + if (ret) { + dev_err(dev, "failed to set fifo organization\n"); + return ret; + } + + in_word_size = snd_pcm_format_physical_width(config.input_format) / 8; + out_word_size = snd_pcm_format_physical_width(config.output_format) / 8; + + /* allocate dma buffers */ + m2m->dma_block[IN].length = EASRC_DMA_BUFFER_SIZE; + m2m->dma_block[IN].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE, + in_word_size * ctx->channels); + m2m->dma_block[OUT].length = EASRC_DMA_BUFFER_SIZE; + m2m->dma_block[OUT].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE, + out_word_size * ctx->channels); + + ret = fsl_allocate_dma_buf(m2m); + if (ret) { + dev_err(dev, "failed to allocate DMA buffers: %d\n", ret); + return ret; + } + + ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN); + if (!ctx->dma_chan[IN]) { + dev_err(dev, "[ctx%d] failed to get input DMA channel\n", + m2m->ctx->index); + return -EBUSY; + } + ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT); + if (!ctx->dma_chan[OUT]) { + dev_err(dev, "[ctx%d] failed to get output DMA channel\n", + m2m->ctx->index); + return -EBUSY; + } + + ret = copy_to_user(user, &config, sizeof(config)); + if (ret) { + dev_err(dev, "failed to send config to user: %d\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_release_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + struct device *dev = &easrc->pdev->dev; + enum asrc_pair_index index; + unsigned long lock_flags; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, + "[ctx%d] failed to get index from user space %d\n", + m2m->ctx->index, ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, + "[ctx%d] releasing wrong context - %d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + if (m2m->easrc_active) { + m2m->easrc_active = 0; + fsl_easrc_stop_context(ctx); + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->ctx_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (ctx->dma_chan[IN]) + dma_release_channel(ctx->dma_chan[IN]); + if (ctx->dma_chan[OUT]) + dma_release_channel(ctx->dma_chan[OUT]); + + ctx->dma_chan[IN] = NULL; + ctx->dma_chan[OUT] = NULL; + + /* free buffers allocated in config context*/ + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_easrc_release_context(ctx); + + return 0; +} + +static long fsl_easrc_ioctl_convert(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = m2m->ctx; + struct asrc_convert_buffer buf; + int ret; + + ret = copy_from_user(&buf, user, sizeof(buf)); + if (ret) { + dev_err(dev, "failed to get buf from user space: %d\n", ret); + return ret; + } + + /* fsl_easrc_calc_last_period_size(ctx, &buf); */ + ret = fsl_easrc_prepare_buffer(m2m, &buf); + if (ret) { + dev_err(dev, "failed to prepare buffer\n"); + return ret; + } + + reinit_completion(&m2m->complete[IN]); + reinit_completion(&m2m->complete[OUT]); + + fsl_easrc_submit_dma(m2m); + + if (m2m->first_convert) { + fsl_easrc_start_context(ctx); + m2m->first_convert = 0; + } + + ret = fsl_easrc_process_buffer(m2m, &buf); + if (ret) { + dev_err(dev, "failed to process buffer %d\n", ret); + return ret; + } + + ret = copy_to_user(user, &buf, sizeof(buf)); + if (ret) { + dev_err(dev, "failed to send buffer to user: %d\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_start_conv(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + enum asrc_pair_index index; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, "failed to get index from user space: %d\n", + ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, "[ctx%d] attempting to start wrong context%d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + m2m->easrc_active = 1; + m2m->first_convert = 1; + + return 0; +} + +static long fsl_easrc_ioctl_stop_conv(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + struct device *dev = &easrc->pdev->dev; + enum asrc_pair_index index; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, "failed to get index from user space: %d\n", + ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, "[ctx%d] attempting to start wrong context%d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + dmaengine_terminate_all(ctx->dma_chan[IN]); + dmaengine_terminate_all(ctx->dma_chan[OUT]); + + fsl_easrc_stop_context(ctx); + m2m->easrc_active = 0; + + return 0; +} + +static long fsl_easrc_ioctl_status(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = m2m->ctx; + struct asrc_status_flags flags; + int ret; + + ret = copy_from_user(&flags, user, sizeof(flags)); + if (ret) { + dev_err(dev, + "[ctx%d] failed to get flags from user space: %d\n", + m2m->ctx->index, ret); + return ret; + } + + if (m2m->ctx->index != flags.index) { + dev_err(dev, "[ctx%d] getting status for other context: %d\n", + m2m->ctx->index, flags.index); + return -EINVAL; + } + + fsl_easrc_get_status(ctx, &flags); + + ret = copy_to_user(user, &flags, sizeof(flags)); + if (ret) + dev_err(dev, "[ctx%d] failed to send flags to user space\n", + m2m->ctx->index); + + return ret; +} + +static long fsl_easrc_ioctl_flush(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_easrc *easrc = m2m->easrc; + struct device *dev = &easrc->pdev->dev; + struct fsl_easrc_context *ctx = m2m->ctx; + + /* Release DMA and request again */ + dma_release_channel(ctx->dma_chan[IN]); + dma_release_channel(ctx->dma_chan[OUT]); + + ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN); + if (!ctx->dma_chan[IN]) { + dev_err(dev, "failed to request input task DMA channel\n"); + return -EBUSY; + } + + ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT); + if (!ctx->dma_chan[OUT]) { + dev_err(dev, "failed to request output task DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static int fsl_easrc_open(struct inode *inode, struct file *file) +{ + struct miscdevice *easrc_miscdev = file->private_data; + struct fsl_easrc *easrc = dev_get_drvdata(easrc_miscdev->parent); + struct fsl_easrc_m2m *m2m; + struct fsl_easrc_context *ctx; + struct device *dev = &easrc->pdev->dev; + int ret; + + ret = signal_pending(current); + if (ret) { + dev_err(dev, "current process has a signal pending\n"); + return ret; + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + /* set the pointer to easrc private data */ + m2m = kzalloc(sizeof(*m2m), GFP_KERNEL); + if (!m2m) { + ret = -ENOMEM; + goto out; + } + /* just save the pointer to easrc private data */ + m2m->easrc = easrc; + m2m->ctx = ctx; + ctx->easrc = easrc; + ctx->private_data = m2m; + + spin_lock_init(&m2m->lock); + init_completion(&m2m->complete[IN]); + init_completion(&m2m->complete[OUT]); + + /* context structs are already allocated in fsl_easrc->ctx[i] */ + file->private_data = m2m; + + pm_runtime_get_sync(dev); + + return 0; +out: + kfree(ctx); + return ret; +} + +static int fsl_easrc_close(struct inode *inode, struct file *file) +{ + struct fsl_easrc_m2m *m2m = file->private_data; + struct fsl_easrc *easrc = m2m->easrc; + struct fsl_easrc_context *ctx = m2m->ctx; + struct device *dev = &easrc->pdev->dev; + unsigned long lock_flags; + + if (m2m->easrc_active) { + m2m->easrc_active = 0; + dmaengine_terminate_all(ctx->dma_chan[IN]); + dmaengine_terminate_all(ctx->dma_chan[OUT]); + + fsl_easrc_stop_context(ctx); + fsl_easrc_input_dma_callback((void *)m2m); + fsl_easrc_output_dma_callback((void *)m2m); + } + + if (!ctx) + goto null_ctx; + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (m2m->ctx_hold) { + m2m->ctx_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (ctx->dma_chan[IN]) + dma_release_channel(ctx->dma_chan[IN]); + if (ctx->dma_chan[OUT]) + dma_release_channel(ctx->dma_chan[OUT]); + + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_easrc_release_context(ctx); + } else { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + } + +null_ctx: + spin_lock_irqsave(&easrc->lock, lock_flags); + kfree(m2m); + kfree(ctx); + file->private_data = NULL; + spin_unlock_irqrestore(&easrc->lock, lock_flags); + + pm_runtime_put_sync(dev); + + return 0; +} + +static long fsl_easrc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct fsl_easrc_m2m *m2m = file->private_data; + struct fsl_easrc *easrc = m2m->easrc; + void __user *user = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case ASRC_REQ_PAIR: + ret = fsl_easrc_ioctl_req_context(m2m, user); + break; + case ASRC_CONFIG_PAIR: + ret = fsl_easrc_ioctl_config_context(m2m, user); + break; + case ASRC_RELEASE_PAIR: + ret = fsl_easrc_ioctl_release_context(m2m, user); + break; + case ASRC_CONVERT: + ret = fsl_easrc_ioctl_convert(m2m, user); + break; + case ASRC_START_CONV: + ret = fsl_easrc_ioctl_start_conv(m2m, user); + break; + case ASRC_STOP_CONV: + ret = fsl_easrc_ioctl_stop_conv(m2m, user); + break; + case ASRC_STATUS: + ret = fsl_easrc_ioctl_status(m2m, user); + break; + case ASRC_FLUSH: + ret = fsl_easrc_ioctl_flush(m2m, user); + break; + default: + dev_err(&easrc->pdev->dev, "invalid ioctl command\n"); + } + + return ret; +} + +static const struct file_operations easrc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_easrc_ioctl, + .open = fsl_easrc_open, + .release = fsl_easrc_close, +}; + +static int fsl_easrc_m2m_init(struct fsl_easrc *easrc) +{ + struct device *dev = &easrc->pdev->dev; + int ret; + + easrc->easrc_miscdev.fops = &easrc_fops; + easrc->easrc_miscdev.parent = dev; + easrc->easrc_miscdev.name = easrc->name; + easrc->easrc_miscdev.minor = MISC_DYNAMIC_MINOR; + ret = misc_register(&easrc->easrc_miscdev); + if (ret) + dev_err(dev, "failed to register char device %d\n", ret); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static void fsl_easrc_m2m_suspend(struct fsl_easrc *easrc) +{ + struct fsl_easrc_context *ctx; + struct fsl_easrc_m2m *m2m; + unsigned long lock_flags; + int i; + + for (i = 0; i < EASRC_CTX_MAX_NUM; i++) { + spin_lock_irqsave(&easrc->lock, lock_flags); + ctx = easrc->ctx[i]; + if (!ctx || !ctx->private_data) { + spin_unlock_irqrestore(&easrc->lock, lock_flags); + continue; + } + m2m = ctx->private_data; + + if (!completion_done(&m2m->complete[IN])) { + if (ctx->dma_chan[IN]) + dmaengine_terminate_all(ctx->dma_chan[IN]); + fsl_easrc_input_dma_callback((void *)m2m); + } + if (!completion_done(&m2m->complete[OUT])) { + if (ctx->dma_chan[OUT]) + dmaengine_terminate_all(ctx->dma_chan[OUT]); + fsl_easrc_output_dma_callback((void *)m2m); + } + + m2m->first_convert = 1; + m2m->in_filled_len = 0; + fsl_easrc_stop_context(ctx); + spin_unlock_irqrestore(&easrc->lock, lock_flags); + } +} + +static void fsl_easrc_m2m_resume(struct fsl_easrc *easrc) +{ + /* null */ +} +#endif diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 33ade79fa032..337ccf289e53 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -14,6 +14,7 @@ #include <sound/pcm_params.h> #include "fsl_esai.h" +#include "fsl_esai_mix.h" #include "imx-pcm.h" #define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ @@ -21,59 +22,32 @@ SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) -/** - * fsl_esai: ESAI private data - * - * @dma_params_rx: DMA parameters for receive channel - * @dma_params_tx: DMA parameters for transmit channel - * @pdev: platform device pointer - * @regmap: regmap handler - * @coreclk: clock source to access register - * @extalclk: esai clock source to derive HCK, SCK and FS - * @fsysclk: system clock source to derive HCK, SCK and FS - * @spbaclk: SPBA clock (optional, depending on SoC design) - * @task: tasklet to handle the reset operation - * @lock: spin lock between hw_reset() and trigger() - * @fifo_depth: depth of tx/rx FIFO - * @slot_width: width of each DAI slot - * @slots: number of slots - * @channels: channel num for tx or rx - * @hck_rate: clock rate of desired HCKx clock - * @sck_rate: clock rate of desired SCKx clock - * @hck_dir: the direction of HCKx pads - * @sck_div: if using PSR/PM dividers for SCKx clock - * @slave_mode: if fully using DAI slave mode - * @synchronous: if using tx/rx synchronous mode - * @reset_at_xrun: flags for enable reset operaton - * @name: driver name - */ -struct fsl_esai { - struct snd_dmaengine_dai_dma_data dma_params_rx; - struct snd_dmaengine_dai_dma_data dma_params_tx; - struct platform_device *pdev; - struct regmap *regmap; - struct clk *coreclk; - struct clk *extalclk; - struct clk *fsysclk; - struct clk *spbaclk; - struct tasklet_struct task; - spinlock_t lock; /* Protect hw_reset and trigger */ - u32 fifo_depth; - u32 slot_width; - u32 slots; - u32 tx_mask; - u32 rx_mask; - u32 channels[2]; - u32 hck_rate[2]; - u32 sck_rate[2]; - bool hck_dir[2]; - bool sck_div[2]; - bool slave_mode; - bool synchronous; - bool reset_at_xrun; - char name[32]; +static struct fsl_esai_soc_data fsl_esai_vf610 = { + .imx = false, + .reset_at_xrun = true, + .use_edma = false, +}; + +static struct fsl_esai_soc_data fsl_esai_imx35 = { + .imx = true, + .reset_at_xrun = true, + .use_edma = false, +}; + +static struct fsl_esai_soc_data fsl_esai_imx6ull = { + .imx = true, + .reset_at_xrun = false, + .use_edma = false, }; +static struct fsl_esai_soc_data fsl_esai_imx8qm = { + .imx = true, + .reset_at_xrun = false, + .use_edma = true, +}; + +static void fsl_esai_hw_reset(struct fsl_esai *esai_priv); + static irqreturn_t esai_isr(int irq, void *devid) { struct fsl_esai *esai_priv = (struct fsl_esai *)devid; @@ -85,23 +59,19 @@ static irqreturn_t esai_isr(int irq, void *devid) regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr); if ((saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE)) && - esai_priv->reset_at_xrun) { + esai_priv->soc->reset_at_xrun) { dev_dbg(&pdev->dev, "reset module for xrun\n"); - regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, - ESAI_xCR_xEIE_MASK, 0); - regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, - ESAI_xCR_xEIE_MASK, 0); - tasklet_schedule(&esai_priv->task); + fsl_esai_hw_reset(esai_priv); } if (esr & ESAI_ESR_TINIT_MASK) dev_dbg(&pdev->dev, "isr: Transmission Initialized\n"); if (esr & ESAI_ESR_RFF_MASK) - dev_warn(&pdev->dev, "isr: Receiving overrun\n"); + dev_dbg(&pdev->dev, "isr: Receiving overrun\n"); if (esr & ESAI_ESR_TFE_MASK) - dev_warn(&pdev->dev, "isr: Transmission underrun\n"); + dev_dbg(&pdev->dev, "isr: Transmission underrun\n"); if (esr & ESAI_ESR_TLS_MASK) dev_dbg(&pdev->dev, "isr: Just transmitted the last slot\n"); @@ -342,7 +312,7 @@ static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) int ret; /* Don't apply for fully slave mode or unchanged bclk */ - if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq) + if (esai_priv->slave_mode[tx] || esai_priv->sck_rate[tx] == freq) return 0; if (ratio * freq > hck_rate) @@ -451,35 +421,62 @@ static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - esai_priv->slave_mode = false; + if (esai_priv->slave_mode[0] == esai_priv->slave_mode[1]) { + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + esai_priv->slave_mode[0] = true; + esai_priv->slave_mode[1] = true; + break; + case SND_SOC_DAIFMT_CBS_CFM: + xccr |= ESAI_xCCR_xCKD; + break; + case SND_SOC_DAIFMT_CBM_CFS: + xccr |= ESAI_xCCR_xFSD; + break; + case SND_SOC_DAIFMT_CBS_CFS: + xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + esai_priv->slave_mode[0] = false; + esai_priv->slave_mode[1] = false; + break; + default: + return -EINVAL; + } - /* DAI clock master masks */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - esai_priv->slave_mode = true; - break; - case SND_SOC_DAIFMT_CBS_CFM: - xccr |= ESAI_xCCR_xCKD; - break; - case SND_SOC_DAIFMT_CBM_CFS: - xccr |= ESAI_xCCR_xFSD; - break; - case SND_SOC_DAIFMT_CBS_CFS: - xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; - break; - default: - return -EINVAL; + mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | + ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + regmap_update_bits(esai_priv->regmap, + REG_ESAI_TCCR, mask, xccr); + regmap_update_bits(esai_priv->regmap, + REG_ESAI_RCCR, mask, xccr); + + } else { + + mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | + ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + if (esai_priv->slave_mode[0]) + regmap_update_bits(esai_priv->regmap, + REG_ESAI_RCCR, mask, xccr); + else + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, + mask, + xccr | ESAI_xCCR_xFSD | + ESAI_xCCR_xCKD); + + if (esai_priv->slave_mode[1]) + regmap_update_bits(esai_priv->regmap, + REG_ESAI_TCCR, mask, xccr); + else + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, + mask, + xccr | ESAI_xCCR_xFSD | + ESAI_xCCR_xCKD); } mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR | ESAI_xCR_xWA; regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, xcr); regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, mask, xcr); - mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | - ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; - regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, mask, xccr); - regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, mask, xccr); - return 0; } @@ -487,6 +484,7 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; if (!dai->active) { /* Set synchronous mode */ @@ -503,10 +501,28 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, ESAI_xCCR_xDC(esai_priv->slots)); } + if (esai_priv->soc->use_edma) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + tx ? esai_priv->dma_params_tx.maxburst : + esai_priv->dma_params_rx.maxburst); + if (esai_priv->sw_mix) + fsl_esai_mix_open(substream, &esai_priv->mix[tx]); + return 0; } +static void fsl_esai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + if (esai_priv->sw_mix) + fsl_esai_mix_close(substream, &esai_priv->mix[tx]); +} + static int fsl_esai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -562,6 +578,10 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream, ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO)); regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO)); + + if (esai_priv->sw_mix) + fsl_esai_mix_hw_params(substream, params, &esai_priv->mix[tx]); + return 0; } @@ -680,9 +700,8 @@ static void fsl_esai_trigger_stop(struct fsl_esai *esai_priv, bool tx) ESAI_xFCR_xFR, 0); } -static void fsl_esai_hw_reset(unsigned long arg) +static void fsl_esai_hw_reset(struct fsl_esai *esai_priv) { - struct fsl_esai *esai_priv = (struct fsl_esai *)arg; bool tx = true, rx = false, enabled[2]; unsigned long lock_flags; u32 tfcr, rfcr; @@ -735,13 +754,23 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; unsigned long lock_flags; + u32 state; - esai_priv->channels[tx] = substream->runtime->channels; + if (esai_priv->sw_mix) + esai_priv->channels[tx] = esai_priv->mix[tx].channels; + else + esai_priv->channels[tx] = substream->runtime->channels; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (esai_priv->sw_mix) { + state = atomic_cmpxchg(&esai_priv->mix[tx].active, 0, 1); + if (!state) + fsl_esai_mix_trigger(substream, cmd, &esai_priv->mix[tx]); + } + spin_lock_irqsave(&esai_priv->lock, lock_flags); fsl_esai_trigger_start(esai_priv, tx); spin_unlock_irqrestore(&esai_priv->lock, lock_flags); @@ -749,6 +778,12 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (esai_priv->sw_mix) { + state = atomic_cmpxchg(&esai_priv->mix[tx].active, 1, 0); + if (state) + fsl_esai_mix_trigger(substream, cmd, &esai_priv->mix[tx]); + } + spin_lock_irqsave(&esai_priv->lock, lock_flags); fsl_esai_trigger_stop(esai_priv, tx); spin_unlock_irqrestore(&esai_priv->lock, lock_flags); @@ -762,6 +797,7 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, static const struct snd_soc_dai_ops fsl_esai_dai_ops = { .startup = fsl_esai_startup, + .shutdown = fsl_esai_shutdown, .trigger = fsl_esai_trigger, .hw_params = fsl_esai_hw_params, .set_sysclk = fsl_esai_set_dai_sysclk, @@ -930,6 +966,7 @@ static int fsl_esai_probe(struct platform_device *pdev) const __be32 *iprop; void __iomem *regs; int irq, ret; + unsigned long irqflag = 0; esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL); if (!esai_priv) @@ -938,9 +975,11 @@ static int fsl_esai_probe(struct platform_device *pdev) esai_priv->pdev = pdev; snprintf(esai_priv->name, sizeof(esai_priv->name), "%pOFn", np); - if (of_device_is_compatible(np, "fsl,vf610-esai") || - of_device_is_compatible(np, "fsl,imx35-esai")) - esai_priv->reset_at_xrun = true; + esai_priv->soc = of_device_get_match_data(&pdev->dev); + if (!esai_priv->soc) { + dev_err(&pdev->dev, "failed to get soc data\n"); + return -ENODEV; + } /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -949,7 +988,7 @@ static int fsl_esai_probe(struct platform_device *pdev) return PTR_ERR(regs); esai_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "core", regs, &fsl_esai_regmap_config); + NULL, regs, &fsl_esai_regmap_config); if (IS_ERR(esai_priv->regmap)) { dev_err(&pdev->dev, "failed to init regmap: %ld\n", PTR_ERR(esai_priv->regmap)); @@ -979,10 +1018,16 @@ static int fsl_esai_probe(struct platform_device *pdev) PTR_ERR(esai_priv->spbaclk)); irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); return irq; + } - ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0, + /* ESAI shared interrupt */ + if (of_property_read_bool(np, "shared-interrupt")) + irqflag = IRQF_SHARED; + + ret = devm_request_irq(&pdev->dev, irq, esai_isr, irqflag, esai_priv->name, esai_priv); if (ret) { dev_err(&pdev->dev, "failed to claim irq %u\n", irq); @@ -992,9 +1037,6 @@ static int fsl_esai_probe(struct platform_device *pdev) /* Set a default slot number */ esai_priv->slots = 2; - /* Set a default master/slave state */ - esai_priv->slave_mode = true; - /* Determine the FIFO depth */ iprop = of_get_property(np, "fsl,fifo-depth", NULL); if (iprop) @@ -1004,12 +1046,28 @@ static int fsl_esai_probe(struct platform_device *pdev) esai_priv->dma_params_tx.maxburst = 16; esai_priv->dma_params_rx.maxburst = 16; + esai_priv->dma_params_rx.chan_name = "rx"; + esai_priv->dma_params_tx.chan_name = "tx"; esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR; esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR; esai_priv->synchronous = of_property_read_bool(np, "fsl,esai-synchronous"); + if (!esai_priv->synchronous) { + if (of_property_read_bool(pdev->dev.of_node, "fsl,txm-rxs")) { + /* 0 -- rx, 1 -- tx */ + esai_priv->slave_mode[0] = true; + esai_priv->slave_mode[1] = false; + } + + if (of_property_read_bool(pdev->dev.of_node, "fsl,txs-rxm")) { + /* 0 -- rx, 1 -- tx */ + esai_priv->slave_mode[0] = false; + esai_priv->slave_mode[1] = true; + } + } + /* Implement full symmetry for synchronous mode */ if (esai_priv->synchronous) { fsl_esai_dai.symmetric_rates = 1; @@ -1020,6 +1078,11 @@ static int fsl_esai_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, esai_priv); spin_lock_init(&esai_priv->lock); + + ret = clk_prepare_enable(esai_priv->coreclk); + if (ret) + return ret; + ret = fsl_esai_hw_init(esai_priv); if (ret) return ret; @@ -1033,6 +1096,8 @@ static int fsl_esai_probe(struct platform_device *pdev) regmap_write(esai_priv->regmap, REG_ESAI_RSMA, 0); regmap_write(esai_priv->regmap, REG_ESAI_RSMB, 0); + clk_disable_unprepare(esai_priv->coreclk); + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_esai_component, &fsl_esai_dai, 1); if (ret) { @@ -1040,16 +1105,20 @@ static int fsl_esai_probe(struct platform_device *pdev) return ret; } - tasklet_init(&esai_priv->task, fsl_esai_hw_reset, - (unsigned long)esai_priv); - pm_runtime_enable(&pdev->dev); regcache_cache_only(esai_priv->regmap, true); - ret = imx_pcm_dma_init(pdev, IMX_ESAI_DMABUF_SIZE); - if (ret) - dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + if (of_property_read_bool(pdev->dev.of_node, "client-dais")) { + esai_priv->sw_mix = true; + ret = fsl_esai_mix_probe(&pdev->dev, &esai_priv->mix[0], &esai_priv->mix[1]); + if (ret) + dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + } else { + ret = imx_pcm_platform_register(&pdev->dev); + if (ret) + dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + } return ret; } @@ -1058,16 +1127,19 @@ static int fsl_esai_remove(struct platform_device *pdev) { struct fsl_esai *esai_priv = platform_get_drvdata(pdev); + if (esai_priv->sw_mix) + fsl_esai_mix_remove(&pdev->dev, &esai_priv->mix[0], &esai_priv->mix[1]); + pm_runtime_disable(&pdev->dev); - tasklet_kill(&esai_priv->task); return 0; } static const struct of_device_id fsl_esai_dt_ids[] = { - { .compatible = "fsl,imx35-esai", }, - { .compatible = "fsl,vf610-esai", }, - { .compatible = "fsl,imx6ull-esai", }, + { .compatible = "fsl,imx35-esai", .data = &fsl_esai_imx35 }, + { .compatible = "fsl,vf610-esai", .data = &fsl_esai_vf610 }, + { .compatible = "fsl,imx6ull-esai", .data = &fsl_esai_imx6ull }, + { .compatible = "fsl,imx8qm-esai", .data = &fsl_esai_imx8qm }, {} }; MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); diff --git a/sound/soc/fsl/fsl_esai.h b/sound/soc/fsl/fsl_esai.h index f873588d9045..628d711ac214 100644 --- a/sound/soc/fsl/fsl_esai.h +++ b/sound/soc/fsl/fsl_esai.h @@ -10,6 +10,9 @@ #ifndef _FSL_ESAI_DAI_H #define _FSL_ESAI_DAI_H +#include <sound/dmaengine_pcm.h> +#include "fsl_esai_mix.h" + /* ESAI Register Map */ #define REG_ESAI_ETDR 0x00 #define REG_ESAI_ERDR 0x04 @@ -348,4 +351,71 @@ #define ESAI_RX_DIV_PSR 3 #define ESAI_RX_DIV_PM 4 #define ESAI_RX_DIV_FP 5 + +/** + * fsl_esai_soc_data: soc specific data + * + * @imx: for imx platform + * @reset_at_xrun: flags for enable reset operaton + * @use_edma: edma is used. + */ +struct fsl_esai_soc_data { + bool imx; + bool reset_at_xrun; + bool use_edma; +}; + +/** + * fsl_esai: ESAI private data + * + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @pdev: platform device pointer + * @regmap: regmap handler + * @coreclk: clock source to access register + * @extalclk: esai clock source to derive HCK, SCK and FS + * @fsysclk: system clock source to derive HCK, SCK and FS + * @spbaclk: SPBA clock (optional, depending on SoC design) + * @soc: soc specific data + * @lock: spin lock between hw_reset() and trigger() + * @fifo_depth: depth of tx/rx FIFO + * @slot_width: width of each DAI slot + * @slots: number of slots + * @channels: channel num for tx or rx + * @hck_rate: clock rate of desired HCKx clock + * @sck_rate: clock rate of desired SCKx clock + * @hck_dir: the direction of HCKx pads + * @sck_div: if using PSR/PM dividers for SCKx clock + * @slave_mode: if fully using DAI slave mode + * @synchronous: if using tx/rx synchronous mode + * @sw_mix: enable sw mix in driver + * @name: driver name + */ +struct fsl_esai { + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct platform_device *pdev; + struct regmap *regmap; + struct clk *coreclk; + struct clk *extalclk; + struct clk *fsysclk; + struct clk *spbaclk; + const struct fsl_esai_soc_data *soc; + struct fsl_esai_mix mix[2]; + spinlock_t lock; /* Protect hw_reset and trigger */ + u32 fifo_depth; + u32 slot_width; + u32 slots; + u32 tx_mask; + u32 rx_mask; + u32 channels[2]; + u32 hck_rate[2]; + u32 sck_rate[2]; + bool hck_dir[2]; + bool sck_div[2]; + bool slave_mode[2]; + bool synchronous; + bool sw_mix; + char name[32]; +}; #endif /* _FSL_ESAI_DAI_H */ diff --git a/sound/soc/fsl/fsl_esai_client.c b/sound/soc/fsl/fsl_esai_client.c new file mode 100644 index 000000000000..d8ebef759153 --- /dev/null +++ b/sound/soc/fsl/fsl_esai_client.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP +/* + * Just allocate memory for FE + * + */ +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "fsl_esai_client.h" +#include "imx-pcm.h" + +static struct snd_pcm_hardware fsl_esai_client_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 2048, /* fix period size, for alignment in FE/BE */ + .period_bytes_max = 2048, + .periods_min = 8, /* periods > 4 ( 4 buffer in BE) */ + .periods_max = 255, + .fifo_size = 0, +}; + +static int fsl_esai_client_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai_client *client = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + snd_pcm_set_runtime_buffer(substream, &client->dma[tx].dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + client->dma[tx].channels = params_channels(params); + client->dma[tx].word_width = snd_pcm_format_physical_width(params_format(params)) / 8; + + return 0; +} + +static int fsl_esai_client_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static snd_pcm_uframes_t fsl_esai_client_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai_client *client = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + return bytes_to_frames(substream->runtime, client->dma[tx].buffer_offset); +} + +static int fsl_esai_client_pcm_open(struct snd_pcm_substream *substream) +{ + int ret; + + snd_soc_set_runtime_hwparams(substream, &fsl_esai_client_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; +} + +int fsl_esai_client_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai_client *client = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* These info are needed by esai mix*/ + client->dma[tx].buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + client->dma[tx].period_bytes = snd_pcm_lib_period_bytes(substream); + client->dma[tx].buffer_offset = 0; + client->dma[tx].active = true; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + client->dma[tx].active = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_esai_client_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_wc(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops fsl_esai_client_pcm_ops = { + .open = fsl_esai_client_pcm_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_esai_client_pcm_hw_params, + .hw_free = fsl_esai_client_pcm_hw_free, + .pointer = fsl_esai_client_pcm_pointer, + .mmap = fsl_esai_client_pcm_mmap, + .trigger = fsl_esai_client_pcm_trigger, +}; + +static int fsl_esai_client_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + return 0; +} + +static const struct snd_soc_component_driver fsl_esai_client_pcm_platform = { + .name = "fsl_esai_client_pcm", + .ops = &fsl_esai_client_pcm_ops, + .pcm_new = fsl_esai_client_pcm_new, +}; + +static const struct snd_soc_component_driver fsl_esai_client_component = { + .name = "fsl-esai-client", +}; + +/* Fix the sample rate/format/channels, for we don't support + * conversion when do mixing. + */ +#define FSL_ESAI_CLIENT_RATES SNDRV_PCM_RATE_48000 +#define FSL_ESAI_CLIENT_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +static struct snd_soc_dai_driver fsl_esai_client_dai_template = { + .name = "fsl-esai-client-dai", + .playback = { + .stream_name = "CLIENT-Playback", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_ESAI_CLIENT_RATES, + .formats = FSL_ESAI_CLIENT_FORMATS, + }, + .capture = { + .stream_name = "CLIENT-Capture", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_ESAI_CLIENT_RATES, + .formats = FSL_ESAI_CLIENT_FORMATS, + }, +}; + +static int fsl_esai_client_probe(struct platform_device *pdev) +{ + struct fsl_esai_client *client; + int ret; + + client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + memcpy(&client->cpu_dai_drv, &fsl_esai_client_dai_template, + sizeof(fsl_esai_client_dai_template)); + + platform_set_drvdata(pdev, client); + + ret = of_property_read_u32(pdev->dev.of_node, "fsl,client-id", &client->id); + if (ret < 0) + return ret; + + if (client->id == 0) { + client->cpu_dai_drv.name = "fsl-client0-dai"; + client->cpu_dai_drv.playback.stream_name = "CLIENT0-Playback"; + client->cpu_dai_drv.capture.stream_name = "CLIENT0-Capture"; + } + + if (client->id == 1) { + client->cpu_dai_drv.name = "fsl-client1-dai"; + client->cpu_dai_drv.playback.stream_name = "CLIENT1-Playback"; + client->cpu_dai_drv.capture.stream_name = "CLIENT1-Capture"; + } + + /* alloc memory for user read data from it. rx. */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + &pdev->dev, + fsl_esai_client_pcm_hardware.buffer_bytes_max, + &client->dma[0].dma_buffer); + if (ret) + return ret; + + /* alloc memory for user write data from it. tx. */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + &pdev->dev, + fsl_esai_client_pcm_hardware.buffer_bytes_max, + &client->dma[1].dma_buffer); + if (ret) + return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &fsl_esai_client_component, &client->cpu_dai_drv, 1); + if (ret < 0) + return ret; + + return devm_snd_soc_register_component(&pdev->dev, &fsl_esai_client_pcm_platform, NULL, 0); +} + +static int fsl_esai_client_remove(struct platform_device *pdev) +{ + struct fsl_esai_client *client = platform_get_drvdata(pdev); + + snd_dma_free_pages(&client->dma[1].dma_buffer); + snd_dma_free_pages(&client->dma[0].dma_buffer); + + return 0; +} + +static const struct of_device_id fsl_esai_client_ids[] = { + { .compatible = "fsl,esai-client", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_esai_client_ids); + +static struct platform_driver fsl_esai_client_driver = { + .probe = fsl_esai_client_probe, + .remove = fsl_esai_client_remove, + .driver = { + .name = "fsl-esai-client", + .of_match_table = fsl_esai_client_ids, + }, +}; +module_platform_driver(fsl_esai_client_driver); + +MODULE_DESCRIPTION("client cpu dai Interface"); +MODULE_ALIAS("platform:fsl-esai-client"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_esai_client.h b/sound/soc/fsl/fsl_esai_client.h new file mode 100644 index 000000000000..684300383b80 --- /dev/null +++ b/sound/soc/fsl/fsl_esai_client.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __FSL_ESAI_CLIENT_H +#define __FSL_ESAI_CLIENT_H + +/** + * fsl_esai_client_dma: esai dma client + * @dma_buffer: structure of dma buffer + * @buffer_bytes: buffer size in bytes + * @period_bytes: period size in bytes + * @period_num: period number + * @buffer_offset: read offset of buffer + * @channels: channel number + * @word_width: word width in bytes + * @active: dma transfer is active + */ +struct fsl_esai_client_dma { + struct snd_dma_buffer dma_buffer; + int buffer_bytes; + int period_bytes; + int period_num; + int buffer_offset; + int channels; + int word_width; + bool active; +}; + +/** + * fsl_esai_client: esai client + * @cpu_dai_drv: CPU DAI driver for this device + * @dma: dma instance for playback and capture + * @id: client index + */ +struct fsl_esai_client { + struct snd_soc_dai_driver cpu_dai_drv; + struct fsl_esai_client_dma dma[2]; + int id; +}; + +#endif /* __FSL_ESAI_CLIENT_H */ diff --git a/sound/soc/fsl/fsl_esai_mix.c b/sound/soc/fsl/fsl_esai_mix.c new file mode 100644 index 000000000000..9b603f9a6e6f --- /dev/null +++ b/sound/soc/fsl/fsl_esai_mix.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP +/* + * Support mix two streams for ESAI + * + */ +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "imx-pcm.h" +#include "fsl_esai_client.h" +#include "fsl_esai.h" +#include "fsl_esai_mix.h" + +int fsl_esai_mix_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct fsl_esai_mix *mix) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_config config; + int err = 0; + + mix->channels = params_channels(params); + mix->word_width = snd_pcm_format_physical_width(params_format(params)) / 8; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + if (!dma_data) + return 0; + + /* fills in addr_width and direction */ + err = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (err) + return err; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, + dma_data, + &config); + + return dmaengine_slave_config(mix->chan, &config); +} + +int fsl_esai_mix_open(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + mix->chan = dma_request_slave_channel(rtd->cpu_dai->dev, + dma_data->chan_name); + + return 0; +} + +int fsl_esai_mix_close(struct snd_pcm_substream *substream, + struct fsl_esai_mix *mix) +{ + dmaengine_synchronize(mix->chan); + dma_release_channel(mix->chan); + + return 0; +} + +static int fsl_esai_mix_pointer(struct fsl_esai_mix *mix) +{ + struct dma_tx_state state; + enum dma_status status; + unsigned int buf_size; + unsigned int pos = 0; + + status = dmaengine_tx_status(mix->chan, mix->cookie, &state); + if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) { + buf_size = mix->buffer_bytes; + if (state.residue > 0 && state.residue <= buf_size) + pos = buf_size - state.residue; + } + + return pos; +} + +static int fsl_esai_tx_avail(struct fsl_esai_mix *mix) +{ + int avail; + + mix->buffer_read_offset = fsl_esai_mix_pointer(mix); + + avail = mix->buffer_bytes - mix->buffer_write_offset + mix->buffer_read_offset; + if (avail < 0) + avail += mix->buffer_bytes; + else if (avail > mix->buffer_bytes) + avail -= mix->buffer_bytes; + + return avail; +} + +static int fsl_esai_rx_avail(struct fsl_esai_mix *mix) +{ + int avail; + + mix->buffer_write_offset = fsl_esai_mix_pointer(mix); + + avail = mix->buffer_bytes - mix->buffer_read_offset + mix->buffer_write_offset; + if (avail < 0) + avail += mix->buffer_bytes; + else if (avail > mix->buffer_bytes) + avail = avail - mix->buffer_bytes; + + return avail; +} + +static void fsl_esai_mix_buffer_from_fe_tx(struct snd_pcm_substream *substream, bool elapse) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + struct fsl_esai_mix *mix = &esai->mix[tx]; + struct fsl_esai_client *client; + struct fsl_esai_client_dma *client_dma; + struct snd_soc_dpcm *dpcm; + unsigned long flags; + int sample_offset = 0; + int client_chn = 0; + int mix_chn = 0; + int sdo_cnt = 0; + int loop_cnt = 0; + int avail = 0; + int size = 0; + int id = 0; + int i = 0, j = 0; + int dst_idx; + u16 *src16; + u16 *dst16; + + for (j = 0; j < MAX_CLIENT_NUM; j++) { + mix->fe_substream[j] = NULL; + mix->client[j] = NULL; + } + + /* Get the active client */ + spin_lock_irqsave(&rtd->card->dpcm_lock, flags); + for_each_dpcm_fe(rtd, substream->stream, dpcm) { + if (dpcm->be != rtd) + continue; + + mix->fe_substream[i] = snd_soc_dpcm_get_substream(dpcm->fe, substream->stream); + mix->client[i] = snd_soc_dai_get_drvdata(dpcm->fe->cpu_dai); + + i++; + if (i >= MAX_CLIENT_NUM) + break; + } + spin_unlock_irqrestore(&rtd->card->dpcm_lock, flags); + + avail = fsl_esai_tx_avail(mix); + if (avail >= mix->buffer_bytes && elapse) + dev_err(rtd->cpu_dai->dev, "mix underrun\n"); + + while (avail >= mix->period_bytes) { + size = mix->period_bytes; + /* mix->word_width == client->word_width */ + /* Mix the internal buffer */ + dst16 = (u16 *)(mix->dma_buffer.area + (mix->buffer_write_offset % mix->buffer_bytes)); + memset(dst16, 0, size); + + for (i = 0; i < mix->client_cnt; i++) { + if (!mix->client[i]) + continue; + + client = mix->client[i]; + client_dma = &client->dma[tx]; + + /* check client is active ? */ + if (client_dma->active) { + sample_offset = 0; + id = client->id; + sdo_cnt = mix->sdo_cnt; + client_chn = client_dma->channels; + mix_chn = mix->channels; + loop_cnt = size / mix->word_width / mix_chn; + + src16 = (u16 *)(client_dma->dma_buffer.area + client_dma->buffer_offset); + dst16 = (u16 *)(mix->dma_buffer.area + (mix->buffer_write_offset % mix->buffer_bytes)); + while (sample_offset < loop_cnt) { + + /* mix the data and reorder it for correct pin */ + for (j = 0; j < client_chn; j++) { + dst_idx = id + j * sdo_cnt; + dst16[dst_idx] = *src16++; + } + + sample_offset++; + dst16 = dst16 + mix_chn; + } + + sample_offset = client_dma->buffer_offset + size / mix->client_cnt; + sample_offset = sample_offset % client_dma->buffer_bytes; + client_dma->buffer_offset = sample_offset; + + if (elapse && mix->fe_substream[i]) + snd_pcm_period_elapsed(mix->fe_substream[i]); + } + } + + mix->buffer_write_offset += size; + if (mix->buffer_write_offset >= mix->buffer_bytes) + mix->buffer_write_offset -= mix->buffer_bytes; + + avail -= mix->period_bytes; + } +} + +static void fsl_esai_split_buffer_from_be_rx(struct snd_pcm_substream *substream, bool elapse) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + struct fsl_esai_mix *mix = &esai->mix[tx]; + struct fsl_esai_client *client; + struct fsl_esai_client_dma *client_dma; + struct snd_soc_dpcm *dpcm; + unsigned long flags; + int sample_offset = 0; + int client_chn = 0; + int mix_chn = 0; + int sdi_cnt = 0; + int loop_cnt = 0; + int id = 0; + int size = 0; + int avail = 0; + int i = 0, j = 0; + int src_idx; + u16 *src16; + u16 *dst16; + + for (j = 0; j < MAX_CLIENT_NUM; j++) { + mix->fe_substream[j] = NULL; + mix->client[j] = NULL; + } + /* Get the active client */ + spin_lock_irqsave(&rtd->card->dpcm_lock, flags); + for_each_dpcm_fe(rtd, substream->stream, dpcm) { + if (dpcm->be != rtd) + continue; + + mix->fe_substream[i] = snd_soc_dpcm_get_substream(dpcm->fe, substream->stream); + mix->client[i] = snd_soc_dai_get_drvdata(dpcm->fe->cpu_dai); + + i++; + if (i >= MAX_CLIENT_NUM) + break; + } + spin_unlock_irqrestore(&rtd->card->dpcm_lock, flags); + + avail = fsl_esai_rx_avail(mix); + if (avail >= mix->buffer_bytes && elapse) + dev_err(rtd->cpu_dai->dev, "mix overrun\n"); + + while (avail >= mix->period_bytes) { + size = mix->period_bytes; + /* mix->word_width == client->word_width */ + /* split the internal buffer */ + for (i = 0; i < mix->client_cnt; i++) { + if (!mix->client[i]) + continue; + + client = mix->client[i]; + client_dma = &client->dma[tx]; + + if (client_dma->active) { + sample_offset = 0; + id = client->id; + sdi_cnt = mix->sdi_cnt; + client_chn = client_dma->channels; + mix_chn = mix->channels; + loop_cnt = size / mix->word_width / mix_chn; + + dst16 = (u16 *)(client_dma->dma_buffer.area + client_dma->buffer_offset); + src16 = (u16 *)(mix->dma_buffer.area + (mix->buffer_read_offset % mix->buffer_bytes)); + while (sample_offset < loop_cnt) { + + /* split the data to corret client*/ + for (j = 0; j < client_chn; j++) { + src_idx = id + j * sdi_cnt; + *dst16++ = src16[src_idx]; + } + + sample_offset++; + src16 = src16 + mix_chn; + } + client_dma->buffer_offset += size / mix->client_cnt; + client_dma->buffer_offset = client_dma->buffer_offset % client_dma->buffer_bytes; + + if (elapse && mix->fe_substream[i]) + snd_pcm_period_elapsed(mix->fe_substream[i]); + } + } + + mix->buffer_read_offset += size; + if (mix->buffer_read_offset >= mix->buffer_bytes) + mix->buffer_read_offset -= mix->buffer_bytes; + + avail -= mix->period_bytes; + } +} + +static void fsl_esai_mix_tx_worker(struct work_struct *work) +{ + struct fsl_esai_mix *mix; + + mix = container_of(work, struct fsl_esai_mix, work); + fsl_esai_mix_buffer_from_fe_tx(mix->substream, true); +} + +static void fsl_esai_mix_rx_worker(struct work_struct *work) +{ + struct fsl_esai_mix *mix; + + mix = container_of(work, struct fsl_esai_mix, work); + fsl_esai_split_buffer_from_be_rx(mix->substream, true); +} + +/* call back of dma event */ +static void fsl_esai_mix_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_esai_mix *mix = &esai->mix[tx]; + + mix->substream = substream; + + queue_work(mix->mix_wq, &mix->work); +} + +static int fsl_esai_mix_prepare_and_submit(struct snd_pcm_substream *substream, + struct fsl_esai_mix *mix) +{ + struct dma_async_tx_descriptor *desc; + enum dma_transfer_direction direction; + unsigned long flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + + direction = snd_pcm_substream_to_dma_direction(substream); + + /* ping-pong buffer for mix */ + desc = dmaengine_prep_dma_cyclic(mix->chan, + mix->dma_buffer.addr, + mix->buffer_bytes, + mix->period_bytes, + direction, flags); + if (!desc) + return -ENOMEM; + + desc->callback = fsl_esai_mix_dma_complete; + desc->callback_param = substream; + mix->cookie = dmaengine_submit(desc); + + /* Mix the tx buffer */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mix->buffer_read_offset = 0; + mix->buffer_write_offset = 0; + fsl_esai_mix_buffer_from_fe_tx(substream, false); + } else { + mix->buffer_read_offset = 0; + mix->buffer_write_offset = 0; + } + + return 0; +} + +int fsl_esai_mix_trigger(struct snd_pcm_substream *substream, int cmd, + struct fsl_esai_mix *mix) +{ + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_esai_mix_prepare_and_submit(substream, mix); + if (ret) + return ret; + dma_async_issue_pending(mix->chan); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + dmaengine_terminate_async(mix->chan); + break; + default: + return -EINVAL; + } + + return 0; +} + +int fsl_esai_mix_probe(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx) +{ + int ret = 0; + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + /** + * initialize info for mixing + * two clients, TX0 pin is for client 0, TX1 pin is for client 1 + * total supported channel is 4. + */ + mix_tx->client_cnt = 2; + mix_tx->sdo_cnt = 2; + mix_tx->sdi_cnt = 2; + mix_tx->channels = 4; + mix_tx->buffer_bytes = 2048 * mix_tx->client_cnt * 4; + mix_tx->period_bytes = 2048 * mix_tx->client_cnt; + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + dev, + IMX_SSI_DMABUF_SIZE * mix_tx->client_cnt, + &mix_tx->dma_buffer); + if (ret) + return ret; + + mix_tx->mix_wq = alloc_ordered_workqueue("esai_mix_tx", WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE); + if (IS_ERR(mix_tx->mix_wq)) + dev_err(dev, "failed create easi mix tx thread\n"); + + INIT_WORK(&mix_tx->work, fsl_esai_mix_tx_worker); + + /** + * initialize info for mixing + * two clients, TX0 pin is for client 0, TX1 pin is for client 1 + * total supported channel is 4. + */ + mix_rx->client_cnt = 2; + mix_rx->sdo_cnt = 2; + mix_rx->sdi_cnt = 2; + mix_rx->channels = 4; + mix_rx->buffer_bytes = 2048 * mix_rx->client_cnt * 4; + mix_rx->period_bytes = 2048 * mix_rx->client_cnt; + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + dev, + IMX_SSI_DMABUF_SIZE * mix_rx->client_cnt, + &mix_rx->dma_buffer); + if (ret) + return ret; + + mix_rx->mix_wq = alloc_ordered_workqueue("esai_mix_rx", WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE); + if (IS_ERR(mix_rx->mix_wq)) + dev_err(dev, "failed create easi mix tx thread\n"); + + INIT_WORK(&mix_rx->work, fsl_esai_mix_rx_worker); + + return ret; +} + +int fsl_esai_mix_remove(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx) +{ + destroy_workqueue(mix_rx->mix_wq); + destroy_workqueue(mix_rx->mix_wq); + + snd_dma_free_pages(&mix_tx->dma_buffer); + snd_dma_free_pages(&mix_rx->dma_buffer); + + return 0; +} +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_esai_mix.h b/sound/soc/fsl/fsl_esai_mix.h new file mode 100644 index 000000000000..c7d2c74d0bcd --- /dev/null +++ b/sound/soc/fsl/fsl_esai_mix.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FSL_ESAI_MIX_H +#define _FSL_ESAI_MIX_H + +/* maximum client number is 4; */ +#define MAX_CLIENT_NUM 4 + +/** + * fsl_esai_mix: esai mix/split data + * @chan: dma channel + * @fe_substream: handler of front end substream + * @client: handler of client + * @dma_buffer: structure of dma buffer + * @buffer_offset: read offset of buffer + * @buffer_bytes: buffer size in bytes + * @period_bytes: period size in bytes + * @period_num: period number + * @word_width: word width in bytes + * @channels: channel number + * @client_cnt: client number, default 2. + * @sdo_cnt: output pin number of esai + * @sdi_cnt: input pin number of esai + * @active: mixer is enabled or not + */ +struct fsl_esai_mix { + struct dma_chan *chan; + struct snd_pcm_substream *fe_substream[MAX_CLIENT_NUM]; + struct fsl_esai_client *client[MAX_CLIENT_NUM]; + struct snd_dma_buffer dma_buffer; + struct workqueue_struct *mix_wq; + struct work_struct work; + struct snd_pcm_substream *substream; + dma_cookie_t cookie; + u32 buffer_read_offset; + u32 buffer_write_offset; + u32 buffer_bytes; + u32 period_bytes; + u32 period_num; + u32 word_width; + u32 channels; + u32 client_cnt; + u32 sdo_cnt; + u32 sdi_cnt; + atomic_t active; +}; + +int fsl_esai_mix_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct fsl_esai_mix *mix); +int fsl_esai_mix_open(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix); +int fsl_esai_mix_close(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix); +int fsl_esai_mix_trigger(struct snd_pcm_substream *substream, int cmd, + struct fsl_esai_mix *mix); +int fsl_esai_mix_probe(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx); +int fsl_esai_mix_remove(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx); + +#endif /* _FSL_ESAI_MIX_H */ diff --git a/sound/soc/fsl/fsl_hdmi.c b/sound/soc/fsl/fsl_hdmi.c new file mode 100644 index 000000000000..05e9a7c1c1d9 --- /dev/null +++ b/sound/soc/fsl/fsl_hdmi.c @@ -0,0 +1,750 @@ +/* + * ALSA SoC HDMI Audio Layer for Freescale i.MX + * + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. + * + * Some code from patch_hdmi.c + * Copyright (c) 2008-2010 Intel Corporation. All rights reserved. + * Copyright (c) 2006 ATI Technologies Inc. + * Copyright (c) 2008 NVIDIA Corp. All rights reserved. + * Copyright (c) 2008 Wei Ni <wni@nvidia.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 + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/asoundef.h> +#include <sound/hdmi-codec.h> + +#include <video/mxc_hdmi.h> + +#include "imx-hdmi.h" + + +static struct mxc_edid_cfg edid_cfg; + +static u32 playback_rates[HDMI_MAX_RATES]; +static u32 playback_sample_size[HDMI_MAX_SAMPLE_SIZE]; +static u32 playback_channels[HDMI_MAX_CHANNEL_CONSTRAINTS]; + +static struct snd_pcm_hw_constraint_list playback_constraint_rates; +static struct snd_pcm_hw_constraint_list playback_constraint_bits; +static struct snd_pcm_hw_constraint_list playback_constraint_channels; + +#ifdef DEBUG +static void dumpregs(struct snd_soc_dai *dai) +{ + u32 n, cts; + + cts = (hdmi_readb(HDMI_AUD_CTS3) << 16) | + (hdmi_readb(HDMI_AUD_CTS2) << 8) | + hdmi_readb(HDMI_AUD_CTS1); + + n = (hdmi_readb(HDMI_AUD_N3) << 16) | + (hdmi_readb(HDMI_AUD_N2) << 8) | + hdmi_readb(HDMI_AUD_N1); + + dev_dbg(dai->dev, "HDMI_PHY_CONF0 0x%02x\n", + hdmi_readb(HDMI_PHY_CONF0)); + dev_dbg(dai->dev, "HDMI_MC_CLKDIS 0x%02x\n", + hdmi_readb(HDMI_MC_CLKDIS)); + dev_dbg(dai->dev, "HDMI_AUD_N[1-3] 0x%06x (%d)\n", + n, n); + dev_dbg(dai->dev, "HDMI_AUD_CTS[1-3] 0x%06x (%d)\n", + cts, cts); + dev_dbg(dai->dev, "HDMI_FC_AUDSCONF 0x%02x\n", + hdmi_readb(HDMI_FC_AUDSCONF)); +} +#else +static void dumpregs(struct snd_soc_dai *dai) {} +#endif + +enum cea_speaker_placement { + FL = (1 << 0), /* Front Left */ + FC = (1 << 1), /* Front Center */ + FR = (1 << 2), /* Front Right */ + FLC = (1 << 3), /* Front Left Center */ + FRC = (1 << 4), /* Front Right Center */ + RL = (1 << 5), /* Rear Left */ + RC = (1 << 6), /* Rear Center */ + RR = (1 << 7), /* Rear Right */ + RLC = (1 << 8), /* Rear Left Center */ + RRC = (1 << 9), /* Rear Right Center */ + LFE = (1 << 10), /* Low Frequency Effect */ + FLW = (1 << 11), /* Front Left Wide */ + FRW = (1 << 12), /* Front Right Wide */ + FLH = (1 << 13), /* Front Left High */ + FCH = (1 << 14), /* Front Center High */ + FRH = (1 << 15), /* Front Right High */ + TC = (1 << 16), /* Top Center */ +}; + +/* + * EDID SA bits in the CEA Speaker Allocation data block + */ +static int edid_speaker_allocation_bits[] = { + [0] = FL | FR, + [1] = LFE, + [2] = FC, + [3] = RL | RR, + [4] = RC, + [5] = FLC | FRC, + [6] = RLC | RRC, + [7] = FLW | FRW, + [8] = FLH | FRH, + [9] = TC, + [10] = FCH, +}; + +struct cea_channel_speaker_allocation { + int ca_index; + int speakers[8]; + + /* Derived values, just for convenience */ + int channels; + int spk_mask; +}; + +/* + * This is an ordered list! + * + * The preceding ones have better chances to be selected by + * hdmi_channel_allocation(). + */ +static struct cea_channel_speaker_allocation channel_allocations[] = { + /* channel: 7 6 5 4 3 2 1 0 */ + { .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL },}, + /* 2.1 */ + { .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL },}, + /* Dolby Surround */ + { .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL },}, + { .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL },}, + { .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL },}, + { .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL },}, + { .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL },}, + { .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL },}, + { .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL },}, + /* surround51 */ + { .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL },}, + /* 6.1 */ + { .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL },}, + /* surround71 */ + { .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL },}, + { .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL },}, + { .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL },}, + { .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL },}, + { .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL },}, + { .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL },}, + { .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL },}, + { .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL },}, + { .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL },}, + { .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL },}, + { .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL },}, + { .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL },}, + { .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL },}, +}; + +/* Compute derived values in channel_allocations[] */ +static void init_channel_allocations(void) +{ + struct cea_channel_speaker_allocation *p; + int i, j; + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + p = channel_allocations + i; + p->channels = 0; + p->spk_mask = 0; + for (j = 0; j < ARRAY_SIZE(p->speakers); j++) + if (p->speakers[j]) { + p->channels++; + p->spk_mask |= p->speakers[j]; + } + } +} + +/* + * The transformation takes two steps: + * + * speaker_alloc => (edid_speaker_allocation_bits[]) => spk_mask + * spk_mask => (channel_allocations[]) => CA + * + * TODO: it could select the wrong CA from multiple candidates. +*/ +static int hdmi_channel_allocation(int channels) +{ + int spk_mask = 0, ca = 0, i, tmpchn, tmpspk; + + /* CA defaults to 0 for basic stereo audio */ + if (channels <= 2) + return 0; + + /* + * Expand EDID's speaker allocation mask + * + * EDID tells the speaker mask in a compact(paired) form, + * expand EDID's notions to match the ones used by Audio InfoFrame. + */ + for (i = 0; i < ARRAY_SIZE(edid_speaker_allocation_bits); i++) { + if (edid_cfg.speaker_alloc & (1 << i)) + spk_mask |= edid_speaker_allocation_bits[i]; + } + + /* Search for the first working match in the CA table */ + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + tmpchn = channel_allocations[i].channels; + tmpspk = channel_allocations[i].spk_mask; + + if (channels == tmpchn && (spk_mask & tmpspk) == tmpspk) { + ca = channel_allocations[i].ca_index; + break; + } + } + + return ca; +} + +static void hdmi_set_audio_infoframe(unsigned int channels) +{ + u8 audiconf0, audiconf2; + + /* + * From CEA-861-D spec: + * HDMI requires the CT, SS and SF fields to be set to 0 ("Refer + * to Stream Header") as these items are carried in the audio stream. + * + * So we only set the CC and CA fields. + */ + audiconf0 = ((channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET) & + HDMI_FC_AUDICONF0_CC_MASK; + + audiconf2 = hdmi_channel_allocation(channels); + + hdmi_writeb(audiconf0, HDMI_FC_AUDICONF0); + hdmi_writeb(0, HDMI_FC_AUDICONF1); + hdmi_writeb(audiconf2, HDMI_FC_AUDICONF2); + hdmi_writeb(0, HDMI_FC_AUDICONF3); +} + +static int cea_audio_rates[HDMI_MAX_RATES] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, +}; + +static void fsl_hdmi_get_playback_rates(void) +{ + int i, count = 0; + u8 rates; + + /* Always assume basic audio support */ + rates = edid_cfg.sample_rates | 0x7; + + for (i = 0 ; i < HDMI_MAX_RATES ; i++) + if ((rates & (1 << i)) != 0) + playback_rates[count++] = cea_audio_rates[i]; + + playback_constraint_rates.list = playback_rates; + playback_constraint_rates.count = count; + + for (i = 0 ; i < playback_constraint_rates.count ; i++) + pr_debug("%s: constraint = %d Hz\n", __func__, playback_rates[i]); +} + +static void fsl_hdmi_get_playback_sample_size(void) +{ + int i = 0; + + /* Always assume basic audio support */ + playback_sample_size[i++] = 16; + + if (edid_cfg.sample_sizes & 0x4) + playback_sample_size[i++] = 24; + + playback_constraint_bits.list = playback_sample_size; + playback_constraint_bits.count = i; + + for (i = 0 ; i < playback_constraint_bits.count ; i++) + pr_debug("%s: constraint = %d bits\n", __func__, playback_sample_size[i]); +} + +static void fsl_hdmi_get_playback_channels(void) +{ + int channels = 2, i = 0; + + /* Always assume basic audio support */ + playback_channels[i++] = channels; + channels += 2; + + while ((i < HDMI_MAX_CHANNEL_CONSTRAINTS) && + (channels <= edid_cfg.max_channels)) { + playback_channels[i++] = channels; + channels += 2; + } + + playback_constraint_channels.list = playback_channels; + playback_constraint_channels.count = i; + + for (i = 0 ; i < playback_constraint_channels.count ; i++) + pr_debug("%s: constraint = %d channels\n", __func__, playback_channels[i]); +} + +static int fsl_hdmi_update_constraints(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + hdmi_get_edid_cfg(&edid_cfg); + + fsl_hdmi_get_playback_rates(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &playback_constraint_rates); + if (ret) + return ret; + + fsl_hdmi_get_playback_sample_size(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &playback_constraint_bits); + if (ret) + return ret; + + fsl_hdmi_get_playback_channels(); + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &playback_constraint_channels); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret) + return ret; + + return 0; +} + +static int fsl_hdmi_soc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai); + int ret; + + clk_prepare_enable(hdmi_data->mipi_core_clk); + clk_prepare_enable(hdmi_data->isfr_clk); + clk_prepare_enable(hdmi_data->iahb_clk); + + dev_dbg(dai->dev, "%s hdmi clks: mipi_core: %d isfr:%d iahb:%d\n", __func__, + (int)clk_get_rate(hdmi_data->mipi_core_clk), + (int)clk_get_rate(hdmi_data->isfr_clk), + (int)clk_get_rate(hdmi_data->iahb_clk)); + + ret = fsl_hdmi_update_constraints(substream); + if (ret < 0) + return ret; + + /* Indicates the subpacket represents a flatline sample */ + hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_SAMPFIT, 0x0); + + return 0; +} + +static void fsl_hdmi_soc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(hdmi_data->iahb_clk); + clk_disable_unprepare(hdmi_data->isfr_clk); + clk_disable_unprepare(hdmi_data->mipi_core_clk); +} + +static int fsl_hdmi_soc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + hdmi_set_audio_infoframe(runtime->channels); + hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_LAYOUT, + (runtime->channels > 2) ? 0x1 : 0x0); + hdmi_set_sample_rate(runtime->rate); + dumpregs(dai); + + return 0; +} + +static struct snd_soc_dai_ops fsl_hdmi_soc_dai_ops = { + .startup = fsl_hdmi_soc_startup, + .shutdown = fsl_hdmi_soc_shutdown, + .prepare = fsl_hdmi_soc_prepare, +}; + +/* IEC60958 status functions */ +static int fsl_hdmi_iec_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + + +static int fsl_hdmi_iec_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + + for (i = 0 ; i < 4 ; i++) + uvalue->value.iec958.status[i] = iec_header.status[i]; + + return 0; +} + +static int fsl_hdmi_iec_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + + /* Do not allow professional mode */ + if (uvalue->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL) + return -EPERM; + + for (i = 0 ; i < 4 ; i++) { + iec_header.status[i] = uvalue->value.iec958.status[i]; + pr_debug("%s status[%d]=0x%02x\n", __func__, i, iec_header.status[i]); + } + + return 0; +} + +static int fsl_hdmi_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_channels(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_channels.count; + + return 0; +} + + +static int fsl_hdmi_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_channels(); + + for (i = 0 ; i < playback_constraint_channels.count ; i++) + uvalue->value.integer.value[i] = playback_channels[i]; + + return 0; +} + +static int fsl_hdmi_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_rates(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_rates.count; + + return 0; +} + +static int fsl_hdmi_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_rates(); + + for (i = 0 ; i < playback_constraint_rates.count ; i++) + uvalue->value.integer.value[i] = playback_rates[i]; + + return 0; +} + +static int fsl_hdmi_formats_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_sample_size(); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = playback_constraint_bits.count; + + return 0; +} + +static int fsl_hdmi_formats_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int i; + hdmi_get_edid_cfg(&edid_cfg); + fsl_hdmi_get_playback_sample_size(); + + for (i = 0 ; i < playback_constraint_bits.count ; i++) + uvalue->value.integer.value[i] = playback_sample_size[i]; + + return 0; +} + +static struct snd_kcontrol_new fsl_hdmi_ctrls[] = { + /* Status cchanel controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_iec_info, + .get = fsl_hdmi_iec_get, + .put = fsl_hdmi_iec_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_channels_info, + .get = fsl_hdmi_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_rates_info, + .get = fsl_hdmi_rates_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Formats", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_hdmi_formats_info, + .get = fsl_hdmi_formats_get, + }, +}; + +static int fsl_hdmi_soc_dai_probe(struct snd_soc_dai *dai) +{ + int ret; + + init_channel_allocations(); + + ret = snd_soc_add_dai_controls(dai, fsl_hdmi_ctrls, + ARRAY_SIZE(fsl_hdmi_ctrls)); + if (ret) + dev_warn(dai->dev, "failed to add dai controls\n"); + + return 0; +} + +static struct snd_soc_dai_driver fsl_hdmi_dai = { + .probe = &fsl_hdmi_soc_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 8, + .rates = MXC_HDMI_RATES_PLAYBACK, + .formats = MXC_HDMI_FORMATS_PLAYBACK, + }, + .ops = &fsl_hdmi_soc_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_hdmi_component = { + .name = "fsl-hdmi", +}; + +/* HDMI audio codec callbacks */ +static int fsl_hdmi_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + dev_dbg(dev, "[%s]: %u Hz, %d bit, %d channels\n", __func__, + hparms->sample_rate, hparms->sample_width, hparms->cea.channels); + + return 0; +} + +static void fsl_hdmi_audio_shutdown(struct device *dev, void *data) +{ + dev_dbg(dev, "[%s]\n", __func__); +} + +static const struct hdmi_codec_ops fsl_hdmi_audio_codec_ops = { + .hw_params = fsl_hdmi_audio_hw_params, + .audio_shutdown = fsl_hdmi_audio_shutdown, +}; + +static struct hdmi_codec_pdata codec_data = { + .ops = &fsl_hdmi_audio_codec_ops, + .i2s = 1, + .max_i2s_channels = 8, +}; + +static int fsl_hdmi_dai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct imx_hdmi *hdmi_data; + int ret = 0; + + if (!np) + return -ENODEV; + + if (!hdmi_get_registered()) { + dev_err(&pdev->dev, "failed to probe. Load HDMI-video first.\n"); + return -ENOMEM; + } + + hdmi_data = devm_kzalloc(&pdev->dev, sizeof(*hdmi_data), GFP_KERNEL); + if (!hdmi_data) { + dev_err(&pdev->dev, "failed to alloc hdmi_data\n"); + return -ENOMEM; + } + + hdmi_data->pdev = pdev; + + memcpy(&hdmi_data->cpu_dai_drv, &fsl_hdmi_dai, sizeof(fsl_hdmi_dai)); + hdmi_data->cpu_dai_drv.name = np->name; + + hdmi_data->mipi_core_clk = devm_clk_get(&pdev->dev, "mipi_core"); + if (IS_ERR(hdmi_data->mipi_core_clk)) { + ret = PTR_ERR(hdmi_data->mipi_core_clk); + dev_err(&pdev->dev, "failed to get mipi core clk: %d\n", ret); + return -EINVAL; + } + + hdmi_data->isfr_clk = devm_clk_get(&pdev->dev, "hdmi_isfr"); + if (IS_ERR(hdmi_data->isfr_clk)) { + ret = PTR_ERR(hdmi_data->isfr_clk); + dev_err(&pdev->dev, "failed to get HDMI isfr clk: %d\n", ret); + return -EINVAL; + } + + hdmi_data->iahb_clk = devm_clk_get(&pdev->dev, "hdmi_iahb"); + if (IS_ERR(hdmi_data->iahb_clk)) { + ret = PTR_ERR(hdmi_data->iahb_clk); + dev_err(&pdev->dev, "failed to get HDMI ahb clk: %d\n", ret); + return -EINVAL; + } + + dev_set_drvdata(&pdev->dev, hdmi_data); + ret = snd_soc_register_component(&pdev->dev, &fsl_hdmi_component, + &hdmi_data->cpu_dai_drv, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + return ret; + } + + hdmi_data->codec_dev = platform_device_register_data(&pdev->dev, + HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_NONE, + &codec_data, sizeof(codec_data)); + if (IS_ERR(hdmi_data->codec_dev)) { + dev_err(&pdev->dev, "failed to register HDMI audio codec\n"); + ret = PTR_ERR(hdmi_data->codec_dev); + goto fail; + } + + hdmi_data->dma_dev = platform_device_alloc("imx-hdmi-audio", -1); + if (!hdmi_data->dma_dev) { + ret = -ENOMEM; + goto fail_dma; + } + + platform_set_drvdata(hdmi_data->dma_dev, hdmi_data); + + ret = platform_device_add(hdmi_data->dma_dev); + if (ret) { + platform_device_put(hdmi_data->dma_dev); + goto fail_dma; + } + + return 0; + +fail_dma: + platform_device_unregister(hdmi_data->codec_dev); +fail: + snd_soc_unregister_component(&pdev->dev); + + return ret; +} + +static int fsl_hdmi_dai_remove(struct platform_device *pdev) +{ + struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev); + + platform_device_unregister(hdmi_data->dma_dev); + platform_device_unregister(hdmi_data->codec_dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct of_device_id fsl_hdmi_dai_dt_ids[] = { + { .compatible = "fsl,imx6dl-hdmi-audio", }, + { .compatible = "fsl,imx6q-hdmi-audio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_hdmi_dai_dt_ids); + +static struct platform_driver fsl_hdmi_driver = { + .probe = fsl_hdmi_dai_probe, + .remove = fsl_hdmi_dai_remove, + .driver = { + .name = "fsl-hdmi-dai", + .owner = THIS_MODULE, + .of_match_table = fsl_hdmi_dai_dt_ids, + }, +}; +module_platform_driver(fsl_hdmi_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX HDMI TX DAI"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:fsl-hdmi-dai"); diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index f7f2d29f1bfe..59efd823906f 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright 2018 NXP +#include <linux/atomic.h> #include <linux/clk.h> #include <linux/device.h> #include <linux/interrupt.h> @@ -31,8 +32,12 @@ struct fsl_micfil { struct platform_device *pdev; struct regmap *regmap; const struct fsl_micfil_soc_data *soc; + struct clk *busclk; struct clk *mclk; + struct clk *clk_src[MICFIL_CLK_SRC_NUM]; struct snd_dmaengine_dai_dma_data dma_params_rx; + struct kobject *hwvad_kobject; + unsigned int vad_channel; unsigned int dataline; char name[32]; int irq[MICFIL_IRQ_LINES]; @@ -40,6 +45,25 @@ struct fsl_micfil { int quality; /*QUALITY 2-0 bits */ bool slave_mode; int channel_gain[8]; + int clk_src_id; + int dc_remover; + int vad_sound_gain; + int vad_noise_gain; + int vad_input_gain; + int vad_frame_time; + int vad_init_time; + int vad_init_mode; + int vad_nfil_adjust; + int vad_hpf; + int vad_zcd_th; + int vad_zcd_auto; + int vad_zcd_en; + int vad_zcd_adj; + int vad_rate_index; + atomic_t recording_state; + atomic_t hwvad_state; + /* spinlock to control HWVAD enable/disable */ + spinlock_t hwvad_lock; }; struct fsl_micfil_soc_data { @@ -47,6 +71,12 @@ struct fsl_micfil_soc_data { unsigned int fifo_depth; unsigned int dataline; bool imx; + u64 formats; +}; + +static char *envp[] = { + "EVENT=PDM_VOICE_DETECT", + NULL, }; static struct fsl_micfil_soc_data fsl_micfil_imx8mm = { @@ -54,10 +84,20 @@ static struct fsl_micfil_soc_data fsl_micfil_imx8mm = { .fifos = 8, .fifo_depth = 8, .dataline = 0xf, + .formats = SNDRV_PCM_FMTBIT_S16_LE, +}; + +static struct fsl_micfil_soc_data fsl_micfil_imx8mp = { + .imx = true, + .fifos = 8, + .fifo_depth = 32, + .dataline = 0xf, + .formats = SNDRV_PCM_FMTBIT_S32_LE, }; static const struct of_device_id fsl_micfil_dt_ids[] = { { .compatible = "fsl,imx8mm-micfil", .data = &fsl_micfil_imx8mm }, + { .compatible = "fsl,imx8mp-micfil", .data = &fsl_micfil_imx8mp }, {} }; MODULE_DEVICE_TABLE(of, fsl_micfil_dt_ids); @@ -77,11 +117,525 @@ static const char * const micfil_quality_select_texts[] = { "VLow0", "Low", }; +static const char * const micfil_hwvad_init_mode[] = { + "Envelope mode", "Energy mode", +}; + +static const char * const micfil_hwvad_hpf_texts[] = { + "Filter bypass", + "Cut-off @1750Hz", + "Cut-off @215Hz", + "Cut-off @102Hz", +}; + +static const char * const micfil_hwvad_zcd_enable[] = { + "OFF", "ON", +}; + +static const char * const micfil_hwvad_zcdauto_enable[] = { + "OFF", "ON", +}; + +static const char * const micfil_hwvad_noise_decimation[] = { + "Disabled", "Enabled", +}; + +/* when adding new rate text, also add it to the + * micfil_hwvad_rate_ints + */ +static const char * const micfil_hwvad_rate[] = { + "48KHz", "44.1KHz", +}; + +static const int micfil_hwvad_rate_ints[] = { + 48000, 44100, +}; + +static const char * const micfil_clk_src_texts[] = { + "Auto", "AudioPLL1", "AudioPLL2", "ExtClk3", +}; + +/* DC Remover Control + * Filter Bypassed 1 1 + * Cut-off @21Hz 0 0 + * Cut-off @83Hz 0 1 + * Cut-off @152HZ 1 0 + */ +static const char * const micfil_dc_remover_texts[] = { + "Cut-off @21Hz", "Cut-off @83Hz", + "Cut-off @152Hz", "Bypass", +}; + static const struct soc_enum fsl_micfil_quality_enum = SOC_ENUM_SINGLE(REG_MICFIL_CTRL2, MICFIL_CTRL2_QSEL_SHIFT, ARRAY_SIZE(micfil_quality_select_texts), micfil_quality_select_texts); +static const struct soc_enum fsl_micfil_hwvad_init_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_init_mode), + micfil_hwvad_init_mode); +static const struct soc_enum fsl_micfil_hwvad_hpf_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_hpf_texts), + micfil_hwvad_hpf_texts); +static const struct soc_enum fsl_micfil_hwvad_zcd_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcd_enable), + micfil_hwvad_zcd_enable); +static const struct soc_enum fsl_micfil_hwvad_zcdauto_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcdauto_enable), + micfil_hwvad_zcd_enable); +static const struct soc_enum fsl_micfil_hwvad_ndec_enum = + SOC_ENUM_SINGLE(REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NOREN_SHIFT, + ARRAY_SIZE(micfil_hwvad_noise_decimation), + micfil_hwvad_noise_decimation); +static const struct soc_enum fsl_micfil_hwvad_rate_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_rate), + micfil_hwvad_rate); +static const struct soc_enum fsl_micfil_clk_src_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_clk_src_texts), + micfil_clk_src_texts); +static const struct soc_enum fsl_micfil_dc_remover_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_dc_remover_texts), + micfil_dc_remover_texts); + +static int micfil_put_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->clk_src_id = val; + + return 0; +} + +static int micfil_get_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->clk_src_id; + + return 0; +} + +static int micfil_put_dc_remover_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + unsigned int *item = ucontrol->value.enumerated.item; + int val = snd_soc_enum_item_to_val(e, item[0]); + int i = 0, ret = 0; + u32 reg_val = 0; + + if (val < 0 || val > 3) + return -EINVAL; + + micfil->dc_remover = val; + + /* Calculate total value for all channels */ + for (i = 0; i < 8; i++) + reg_val |= MICFIL_DC_MODE(val, i); + + /* Update DC Remover mode for all channels */ + ret = snd_soc_component_update_bits(comp, REG_MICFIL_DC_CTRL, + MICFIL_DC_CTRL_MASK, reg_val); + if (ret < 0) + return ret; + + return 0; +} + +static int micfil_get_dc_remover_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->dc_remover; + + return 0; +} + +static int hwvad_put_init_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + /* 0 - Envelope-based Mode + * 1 - Energy-based Mode + */ + micfil->vad_init_mode = val; + return 0; +} + +static int hwvad_get_init_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_init_mode; + + return 0; +} + +static int hwvad_put_hpf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + /* 00 - HPF Bypass + * 01 - Cut-off frequency 1750Hz + * 10 - Cut-off frequency 215Hz + * 11 - Cut-off frequency 102Hz + */ + micfil->vad_hpf = val; + + return 0; +} + +static int hwvad_get_hpf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_hpf; + + return 0; +} + +static int hwvad_put_zcd_en(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_zcd_en = val; + + return 0; +} + +static int hwvad_get_zcd_en(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_en; + + return 0; +} + +static int hwvad_put_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_rate_index = val; + + return 0; +} + +static int hwvad_get_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_rate_index; + + return 0; +} + +static int hwvad_put_zcd_auto(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_zcd_auto = val; + + return 0; +} + +static int hwvad_get_zcd_auto(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_auto; + + return 0; +} + +static int gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xf; + + return 0; +} + +static int hwvad_put_input_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_input_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_input_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_input_gain; + + return 0; +} + +static int hwvad_put_sound_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_sound_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_sound_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_sound_gain; + + return 0; +} + +static int hwvad_put_noise_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_noise_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_noise_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_noise_gain; + + return 0; +} + +static int hwvad_framet_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 64; + + return 0; +} + +static int hwvad_put_frame_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_frame_time = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_frame_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_frame_time; + + return 0; +} + +static int hwvad_initt_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 32; + + return 0; +} + +static int hwvad_put_init_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_init_time = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_init_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_init_time; + + return 0; +} + +static int hwvad_nfiladj_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 32; + + return 0; +} + +static int hwvad_put_nfil_adjust(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_nfil_adjust = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_nfil_adjust(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_nfil_adjust; + + return 0; +} + +static int hwvad_zcdth_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 1024; + + return 0; +} + +static int hwvad_put_zcd_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_zcd_th = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_zcd_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_th; + + return 0; +} + +static int hwvad_zcdadj_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 16; + + return 0; +} + +static int hwvad_put_zcd_adj(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_zcd_adj = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_zcd_adj(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_adj; + + return 0; +} static DECLARE_TLV_DB_SCALE(gain_tlv, 0, 100, 0); @@ -105,8 +659,107 @@ static const struct snd_kcontrol_new fsl_micfil_snd_controls[] = { SOC_ENUM_EXT("MICFIL Quality Select", fsl_micfil_quality_enum, snd_soc_get_enum_double, snd_soc_put_enum_double), + SOC_ENUM_EXT("HWVAD Initialization Mode", + fsl_micfil_hwvad_init_mode_enum, + hwvad_get_init_mode, hwvad_put_init_mode), + SOC_ENUM_EXT("HWVAD High-Pass Filter", + fsl_micfil_hwvad_hpf_enum, + hwvad_get_hpf, hwvad_put_hpf), + SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Enable", + fsl_micfil_hwvad_zcd_enum, + hwvad_get_zcd_en, hwvad_put_zcd_en), + SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Auto Threshold", + fsl_micfil_hwvad_zcdauto_enum, + hwvad_get_zcd_auto, hwvad_put_zcd_auto), + SOC_ENUM_EXT("HWVAD Noise OR Enable", + fsl_micfil_hwvad_ndec_enum, + snd_soc_get_enum_double, snd_soc_put_enum_double), + SOC_ENUM_EXT("HWVAD Sampling Rate", + fsl_micfil_hwvad_rate_enum, + hwvad_get_rate, hwvad_put_rate), + SOC_ENUM_EXT("Clock Source", + fsl_micfil_clk_src_enum, + micfil_get_clk_src, micfil_put_clk_src), + SOC_ENUM_EXT("MICFIL DC Remover Control", fsl_micfil_dc_remover_enum, + micfil_get_dc_remover_state, micfil_put_dc_remover_state), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Input Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_input_gain, + .put = hwvad_put_input_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Sound Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_sound_gain, + .put = hwvad_put_sound_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Noise Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_noise_gain, + .put = hwvad_put_noise_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Detector Frame Time", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_framet_info, + .get = hwvad_get_frame_time, + .put = hwvad_put_frame_time, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Detector Initialization Time", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_initt_info, + .get = hwvad_get_init_time, + .put = hwvad_put_init_time, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Noise Filter Adjustment", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_nfiladj_info, + .get = hwvad_get_nfil_adjust, + .put = hwvad_put_nfil_adjust, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Zero-Crossing Detector Threshold", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_zcdth_info, + .get = hwvad_get_zcd_th, + .put = hwvad_put_zcd_th, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Zero-Crossing Detector Adjustment", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_zcdadj_info, + .get = hwvad_get_zcd_adj, + .put = hwvad_put_zcd_adj, + }, + }; +static int disable_hwvad(struct device *dev, bool sync); + + static inline int get_pdm_clk(struct fsl_micfil *micfil, unsigned int rate) { @@ -190,22 +843,596 @@ static int fsl_micfil_reset(struct device *dev) return ret; } + /* w1c */ + regmap_write_bits(micfil->regmap, REG_MICFIL_STAT, 0xFF, 0xFF); + + return 0; +} + +/* enable/disable hwvad interrupts */ +static int configure_hwvad_interrupts(struct device *dev, + int enable) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 vadie_reg = enable ? MICFIL_VAD0_CTRL1_IE : 0; + u32 vaderie_reg = enable ? MICFIL_VAD0_CTRL1_ERIE : 0; + + /* Voice Activity Detector Error Interruption Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ERIE_MASK, + vaderie_reg); + if (ret) { + dev_err(dev, + "Failed to set/clear VADERIE in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + /* Voice Activity Detector Interruption Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_IE_MASK, + vadie_reg); + if (ret) { + dev_err(dev, + "Failed to set/clear VADIE in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + return 0; +} + +static int init_hwvad_internal_filters(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* Voice Activity Detector Internal Filters Initialization*/ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ST10_MASK, + MICFIL_VAD0_CTRL1_ST10); + if (ret) { + dev_err(dev, + "Failed to set VADST10 in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + /* sleep for 100ms - it should be enough for bit to stay + * pulsed for more than 2 cycles + */ + mdelay(MICFIL_SLEEP); + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ST10_MASK, + 0); + if (ret) { + dev_err(dev, + "Failed to clear VADST10 in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + return 0; +} + +/* Zero-Crossing Detector Initialization + * Optionally a Zero-Crossing Detection block (ZCD) could + * be enabled to avoid low energy voiced speech be missed, + * improving the voice detection performance. + * See Section 8.4.3 + */ +static int __maybe_unused init_zcd(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* exit if zcd is not enabled from userspace */ + if (!micfil->vad_zcd_en) + return 0; + + if (micfil->vad_zcd_auto) { + /* Zero-Crossing Detector Adjustment */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDADJ_MASK, + micfil->vad_zcd_adj); + if (ret) { + dev_err(dev, + "Failed to set ZCDADJ in ZCD_VAD0 [%d]\n", + ret); + return ret; + } + } + + /* Zero-Crossing Detector Threshold */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDTH_MASK, + MICFIL_VAD0_ZCD_ZCDTH(micfil->vad_zcd_th)); + if (ret) { + dev_err(dev, "Failed to set ZCDTH in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + /* Zero-Crossing Detector AND Behavior */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDAND_MASK, + MICFIL_HWVAD_ZCDAND); + if (ret) { + dev_err(dev, "Failed to set ZCDAND in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + /* Zero-Crossing Detector Automatic Threshold */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDAUT_MASK, + micfil->vad_zcd_auto); + if (ret) { + dev_err(dev, + "Failed to set/clear ZCDAUT in ZCD_VAD0 [%d]\n", + ret); + return ret; + } + + /* Zero-Crossing Detector Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDEN_MASK, + MICFIL_VAD0_ZCD_ZCDEN); + if (ret) { + dev_err(dev, "Failed to set ZCDEN in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + return 0; +} + +/* Configuration done only in energy-based initialization mode */ +static int init_hwvad_energy_mode(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret, i; + u32 stat; + u32 flag; + + dev_info(dev, "Energy-based mode initialization\n"); + + /* Voice Activity Detector Reset */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + if (ret) { + dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + MICFIL_VAD0_CTRL1_EN); + if (ret) { + dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* it would be a good idea to wait some time before VADEN + * is set + */ + mdelay(5 * MICFIL_SLEEP); + + /* Enable Interrupts */ + ret = configure_hwvad_interrupts(dev, 1); + + /* Initialize Zero Crossing Detector */ + ret = init_zcd(dev); + if (ret) + return ret; + + /* Enable MICFIL module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + /* Wait for INITF to be asserted */ + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_VAD0_STAT); + return ret; + } + + flag = (stat & MICFIL_VAD0_STAT_INITF_MASK); + if (flag == 0) + break; + + mdelay(MICFIL_SLEEP); + } + + if (i == MICFIL_MAX_RETRY) { + dev_err(dev, "initf not asserted. Failed to init hwvad\n"); + return -EBUSY; + } + + /* Initialize Internal Filters */ + ret = init_hwvad_internal_filters(dev); + if (ret) + return ret; + + return ret; +} + +/* Configuration done only in envelope-based initialization mode */ +static int init_hwvad_envelope_mode(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret, i; + u32 stat; + u32 flag; + + /* Frame energy disable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_FRENDIS_MASK, + MICFIL_VAD0_CTRL2_FRENDIS); + if (ret) { + dev_err(dev, "Failed to set FRENDIS in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable pre-filter Noise & Signal */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_PREFEN_MASK, + MICFIL_VAD0_CTRL2_PREFEN); + if (ret) { + dev_err(dev, "Failed to set PREFEN in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable Signal Filter */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SFILEN_MASK, + MICFIL_VAD0_SCONFIG_SFILEN); + if (ret) { + dev_err(dev, + "Failed to set SFILEN in SCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Signal Maximum Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SMAXEN_MASK, + MICFIL_VAD0_SCONFIG_SMAXEN); + if (ret) { + dev_err(dev, + "Failed to set SMAXEN in SCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Allways enable noise filter, not based on voice activity + * information + */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NFILAUT_MASK, + 0); + if (ret) { + dev_err(dev, + "Failed to set NFILAUT in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Noise Minimum Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NMINEN_MASK, + MICFIL_VAD0_NCONFIG_NMINEN); + if (ret) { + dev_err(dev, + "Failed to set NMINEN in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Noise Decimation Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NDECEN_MASK, + MICFIL_VAD0_NCONFIG_NDECEN); + if (ret) { + dev_err(dev, + "Failed to set NDECEN in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Voice Activity Detector Reset */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_MASK, + MICFIL_VAD0_CTRL1_RST); + if (ret) { + dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Initialize Zero Crossing Detector */ + ret = init_zcd(dev); + if (ret) + return ret; + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + MICFIL_VAD0_CTRL1_EN); + if (ret) { + dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable MICFIL module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + /* it would be a good idea to wait some time before VADEN + * is set + */ + mdelay(3 * MICFIL_SLEEP); + + /* Wait for INITF to be asserted */ + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_VAD0_STAT); + return ret; + } + + flag = (stat & MICFIL_VAD0_STAT_INITF_MASK); + if (flag == 0) + break; + + mdelay(MICFIL_SLEEP); + } + + if (i == MICFIL_MAX_RETRY) { + dev_err(dev, "initf not asserted. Failed to init hwvad\n"); + return -EBUSY; + } + + /* Initialize Internal Filters */ + ret = init_hwvad_internal_filters(dev); + if (ret) + return ret; + + /* Enable interrupts */ + ret = configure_hwvad_interrupts(dev, 1); + if (ret) + return ret; + + return ret; +} + +/* Hardware Voice Active Detection: The HWVAD takes data from the input + * of a selected PDM microphone to detect if there is any + * voice activity. When a voice activity is detected, an interrupt could + * be delivered to the system. Initialization in section 8.4: + * Can work in two modes: + * -> Eneveope-based mode (section 8.4.1) + * -> Energy-based mode (section 8.4.2) + * + * It is important to remark that the HWVAD detector could be enabled + * or reset only when the MICFIL isn't running i.e. when the BSY_FIL + * bit in STAT register is cleared + */ +static int __maybe_unused init_hwvad(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 reg_val; + + /* configure CIC OSR in VADCICOSR */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_CICOSR_MASK, + MICFIL_CTRL2_OSR_DEFAULT); + if (ret) { + dev_err(dev, "Failed to set CICOSR in CTRL1_VAD0i [%d]\n", ret); + return ret; + } + + /* configure source channel in VADCHSEL */ + reg_val = MICFIL_VAD0_CTRL1_CHSEL(micfil->vad_channel); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_CHSEL_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set CHSEL in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* configure detector frame time VADFRAMET */ + reg_val = MICFIL_VAD0_CTRL2_FRAMET(micfil->vad_frame_time); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_FRAMET_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set FRAMET in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* configure initialization time in VADINITT */ + reg_val = MICFIL_VAD0_CTRL1_INITT(micfil->vad_init_time); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_INITT_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set INITT in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* configure input gain in VADINPGAIN */ + reg_val = MICFIL_VAD0_CTRL2_INPGAIN(micfil->vad_input_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_INPGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set INPGAIN in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* configure sound gain in SGAIN */ + reg_val = MICFIL_VAD0_SCONFIG_SGAIN(micfil->vad_sound_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set SGAIN in SCONFIG_VAD0 [%d]\n", ret); + return ret; + } + + /* configure noise gain in NGAIN */ + reg_val = MICFIL_VAD0_NCONFIG_NGAIN(micfil->vad_noise_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set NGAIN in NCONFIG_VAD0 [%d]\n", ret); + return ret; + } + + /* configure or clear the VADNFILADJ based on mode */ + reg_val = MICFIL_VAD0_NCONFIG_NFILADJ(micfil->vad_nfil_adjust); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NFILADJ_MASK, + reg_val); + if (ret) { + dev_err(dev, + "Failed to set VADNFILADJ in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* enable the high-pass filter in VADHPF */ + reg_val = MICFIL_VAD0_CTRL2_HPF(micfil->vad_hpf); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_HPF_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set HPF in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* envelope-based specific initialization */ + if (micfil->vad_init_mode == MICFIL_HWVAD_ENVELOPE_MODE) { + ret = init_hwvad_envelope_mode(dev); + if (ret) + return ret; + } else { + ret = init_hwvad_energy_mode(dev); + if (ret) + return ret; + } + return 0; } -static int fsl_micfil_set_mclk_rate(struct fsl_micfil *micfil, +static inline bool clk_in_list(struct clk *p, struct clk *clk_src[]) +{ + int i; + + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) + if (clk_is_match(p, clk_src[i])) + return true; + + return false; +} + +#define CLK_8K_FREQ 24576000 +#define CLK_11K_FREQ 22579200 + +static int fsl_micfil_set_mclk_rate(struct fsl_micfil *micfil, int clk_id, unsigned int freq) { + struct clk *p = micfil->mclk, *pll = 0, *npll = 0; struct device *dev = &micfil->pdev->dev; + u64 ratio = freq; + u64 clk_rate; int ret; + int i; + + /* Do not touch the clock if hwvad is already enabled + * since you can record only at hwvad rate and clock + * has already been set to the required frequency + */ + if (atomic_read(&micfil->hwvad_state) == MICFIL_HWVAD_ON) + return 0; + + /* check if all clock sources are valid */ + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) { + if (micfil->clk_src[i]) + continue; + + dev_err(dev, "Clock Source %d is not valid.\n", i); + return -EINVAL; + } + + while (p) { + struct clk *pp = clk_get_parent(p); + + if (clk_in_list(pp, micfil->clk_src)) { + pll = pp; + break; + } + p = pp; + } + + if (!pll) { + dev_err(dev, "reached a null clock\n"); + return -EINVAL; + } + + if (micfil->clk_src_id == MICFIL_CLK_AUTO) { + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) { + clk_rate = clk_get_rate(micfil->clk_src[i]); + /* This is an workaround since audio_pll2 clock + * has 722534399 rate and this will never divide + * to any known frequency ??? + */ + clk_rate = round_up(clk_rate, 10); + if (do_div(clk_rate, ratio) == 0) + npll = micfil->clk_src[i]; + } + } else { + /* clock id is offseted by 1 since ID=0 means + * auto clock selection + */ + npll = micfil->clk_src[micfil->clk_src_id - 1]; + } + + if (!npll) { + dev_err(dev, + "failed to find a suitable clock source\n"); + return -EINVAL; + } clk_disable_unprepare(micfil->mclk); + if (!clk_is_match(pll, npll)) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(dev, + "failed to set parrent %d\n", ret); + } - ret = clk_set_rate(micfil->mclk, freq * 1024); + clk_rate = freq % 8000 == 0 ? CLK_8K_FREQ : CLK_11K_FREQ; + ret = clk_set_rate(micfil->mclk, clk_rate); if (ret) - dev_warn(dev, "failed to set rate (%u): %d\n", - freq * 1024, ret); - + dev_warn(dev, "failed to set rate (%llu): %d\n", clk_rate, ret); clk_prepare_enable(micfil->mclk); return ret; @@ -298,7 +1525,7 @@ static int fsl_set_clock_params(struct device *dev, unsigned int rate) int clk_div; int ret = 0; - ret = fsl_micfil_set_mclk_rate(micfil, rate); + ret = fsl_micfil_set_mclk_rate(micfil, 0, rate); if (ret < 0) dev_err(dev, "failed to set mclk[%lu] to rate %u\n", clk_get_rate(micfil->mclk), rate); @@ -333,14 +1560,32 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, unsigned int channels = params_channels(params); unsigned int rate = params_rate(params); struct device *dev = &micfil->pdev->dev; + unsigned int hwvad_rate; int ret; + u32 hwvad_state; - /* 1. Disable the module */ - ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, - MICFIL_CTRL1_PDMIEN_MASK, 0); - if (ret) { - dev_err(dev, "failed to disable the module\n"); - return ret; + hwvad_rate = micfil_hwvad_rate_ints[micfil->vad_rate_index]; + hwvad_state = atomic_read(&micfil->hwvad_state); + + /* if hwvad is enabled, make sure you are recording at + * the same rate the hwvad is on or reject it to avoid + * changing the clock rate. + */ + if (hwvad_state == MICFIL_HWVAD_ON && rate != hwvad_rate) { + dev_err(dev, "Record at hwvad rate %u\n", hwvad_rate); + return -EINVAL; + } + + atomic_set(&micfil->recording_state, MICFIL_RECORDING_ON); + + if (hwvad_state == MICFIL_HWVAD_OFF) { + /* 1. Disable the module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, 0); + if (ret) { + dev_err(dev, "failed to disable the module\n"); + return ret; + } } /* enable channels */ @@ -358,11 +1603,22 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, return ret; } + micfil->dma_params_rx.fifo_num = channels; micfil->dma_params_rx.maxburst = channels * MICFIL_DMA_MAXBURST_RX; return 0; } +static int fsl_micfil_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + + atomic_set(&micfil->recording_state, MICFIL_RECORDING_OFF); + + return 0; +} + static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { @@ -374,7 +1630,7 @@ static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, if (!freq) return 0; - ret = fsl_micfil_set_mclk_rate(micfil, freq); + ret = fsl_micfil_set_mclk_rate(micfil, clk_id, freq); if (ret < 0) dev_err(dev, "failed to set mclk[%lu] to rate %u\n", clk_get_rate(micfil->mclk), freq); @@ -382,11 +1638,38 @@ static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, return ret; } +static int fsl_micfil_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + + /* DAI MODE */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + /* DAI CLK INVERSION */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + micfil->slave_mode = false; + + return 0; +} + static struct snd_soc_dai_ops fsl_micfil_dai_ops = { .startup = fsl_micfil_startup, .trigger = fsl_micfil_trigger, .hw_params = fsl_micfil_hw_params, + .hw_free = fsl_micfil_hw_free, .set_sysclk = fsl_micfil_set_dai_sysclk, + .set_fmt = fsl_micfil_set_dai_fmt, }; static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai) @@ -399,17 +1682,30 @@ static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai) /* set qsel to medium */ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2, - MICFIL_CTRL2_QSEL_MASK, MICFIL_MEDIUM_QUALITY); + MICFIL_CTRL2_QSEL_MASK, MICFIL_VLOW0_QUALITY); if (ret) { dev_err(dev, "failed to set quality mode bits, reg 0x%X\n", REG_MICFIL_CTRL2); return ret; } - /* set default gain to max_gain */ - regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x77777777); - for (i = 0; i < 8; i++) - micfil->channel_gain[i] = 0xF; + /* set default gain to 2 */ + regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x22222222); + for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) + micfil->channel_gain[i] = 0xA; + + /* set DC Remover in bypass mode*/ + val = 0; + for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) + val |= MICFIL_DC_MODE(MICFIL_DC_BYPASS, i); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_DC_CTRL, + MICFIL_DC_CTRL_MASK, val); + if (ret) { + dev_err(dev, "failed to set DC Remover mode bits, reg 0x%X\n", + REG_MICFIL_DC_CTRL); + return ret; + } + micfil->dc_remover = MICFIL_DC_BYPASS; snd_soc_dai_init_dma_data(cpu_dai, NULL, &micfil->dma_params_rx); @@ -566,6 +1862,72 @@ static const struct regmap_config fsl_micfil_regmap_config = { /* END OF REGMAP */ +static irqreturn_t voice_detected_fn(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + int ret; + + /* disable hwvad */ + spin_lock(&micfil->hwvad_lock); + ret = disable_hwvad(dev, true); + spin_unlock(&micfil->hwvad_lock); + + if (ret) + dev_err(dev, "Failed to disable HWVAD module: %d\n", ret); + + /* notify userspace that voice was detected */ + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + + return IRQ_HANDLED; +} + +static irqreturn_t hwvad_isr(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + int ret; + u32 vad0_reg; + + regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg); + + /* The only difference between MICFIL_VAD0_STAT_EF and + * MICFIL_VAD0_STAT_IF is that the former requires Write + * 1 to Clear. Since both flags are set, it is enough + * to only read one of them + */ + if (vad0_reg & MICFIL_VAD0_STAT_IF_MASK) { + /* Write 1 to clear */ + regmap_write_bits(micfil->regmap, REG_MICFIL_VAD0_STAT, + MICFIL_VAD0_STAT_IF_MASK, + MICFIL_VAD0_STAT_IF); + + /* disable hwvad interrupts */ + ret = configure_hwvad_interrupts(dev, 0); + if (ret) + dev_err(dev, "Failed to disable interrupts\n"); + } + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t hwvad_err_isr(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + u32 vad0_reg; + + regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg); + + if (vad0_reg & MICFIL_VAD0_STAT_INSATF_MASK) + dev_dbg(dev, "voice activity input overflow/underflow detected\n"); + + if (vad0_reg & MICFIL_VAD0_STAT_INITF_MASK) + dev_dbg(dev, "voice activity dectector is initializing\n"); + + return IRQ_HANDLED; +} + static irqreturn_t micfil_isr(int irq, void *devid) { struct fsl_micfil *micfil = (struct fsl_micfil *)devid; @@ -635,6 +1997,196 @@ static irqreturn_t micfil_err_isr(int irq, void *devid) return IRQ_HANDLED; } +static int fsl_set_clock_params(struct device *, unsigned int); + +static int enable_hwvad(struct device *dev, bool sync) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + int rate; + u32 state; + + if (sync) + pm_runtime_get_sync(dev); + + state = atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_OFF, + MICFIL_HWVAD_ON); + + /* we should not reenable when sync = true because + * this means enable was called for second time by + * user. However state = ON and sync = false can only + * occur when enable is called from system_resume. In + * this case we should enable the hwvad + */ + if (sync && state == MICFIL_HWVAD_ON) { + dev_err(dev, "hwvad already on\n"); + ret = -EBUSY; + goto enable_error; + } + + if (micfil->vad_rate_index >= ARRAY_SIZE(micfil_hwvad_rate_ints)) { + dev_err(dev, "There are more select texts than rates\n"); + ret = -EINVAL; + goto enable_error; + } + + rate = micfil_hwvad_rate_ints[micfil->vad_rate_index]; + + /* This is required because if an arecord was done, + * suspend function will mark regmap as cache only + * and reads/writes in volatile regs will fail + */ + regcache_cache_only(micfil->regmap, false); + regcache_mark_dirty(micfil->regmap); + regcache_sync(micfil->regmap); + + ret = fsl_set_clock_params(dev, rate); + if (ret) + goto enable_error; + + ret = fsl_micfil_reset(dev); + if (ret) + goto enable_error; + + /* Initialize Hardware Voice Activity */ + ret = init_hwvad(dev); + if (ret == 0) + return 0; + +enable_error: + if (state == MICFIL_HWVAD_OFF) + atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_ON, MICFIL_HWVAD_OFF); + if (sync) + pm_runtime_put_sync(dev); + return ret; +} + +static int disable_hwvad(struct device *dev, bool sync) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret = 0; + u32 state; + + /* disable is called with sync = false only from + * system suspend and in this case, you should not + * change the hwvad_state so we know at system_resume + * to reenable hwvad + */ + if (sync) + state = atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_ON, + MICFIL_HWVAD_OFF); + else + state = atomic_read(&micfil->hwvad_state); + + if (state == MICFIL_HWVAD_ON) { + /* This is required because if an arecord was done, + * suspend function will mark regmap as cache only + * and reads/writes in volatile regs will fail + */ + regcache_cache_only(micfil->regmap, false); + regcache_mark_dirty(micfil->regmap); + regcache_sync(micfil->regmap); + + /* Voice Activity Detector Reset */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + + /* Disable HWVAD */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + 0); + + /* Disable Signal Filter */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SFILEN_MASK, + 0); + + /* Signal Maximum Enable */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SMAXEN_MASK, + 0); + + /* Enable pre-filter Noise & Signal */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_PREFEN_MASK, + 0); + + /* Noise Decimation Enable */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NDECEN_MASK, + 0); + + /* disable the module and clock only if recording + * is not done in parallel + */ + state = atomic_read(&micfil->recording_state); + if (state == MICFIL_RECORDING_OFF) { + /* Disable MICFIL module */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + 0); + } + + if (sync) + pm_runtime_put_sync(dev); + } else { + ret = -EPERM; + dev_err(dev, "HWVAD is not enabled %d\n", ret); + } + + return ret; +} + +static ssize_t micfil_hwvad_handler(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + struct kobject *nand_kobj = kobj->parent; + struct device *dev = container_of(nand_kobj, struct device, kobj); + struct fsl_micfil *micfil = dev_get_drvdata(dev); + unsigned long vad_channel, flags; + int ret; + + ret = kstrtoul(buf, 16, &vad_channel); + if (ret < 0) + return -EINVAL; + + spin_lock_irqsave(&micfil->hwvad_lock, flags); + if (vad_channel <= 7) { + micfil->vad_channel = vad_channel; + ret = enable_hwvad(dev, true); + } else { + micfil->vad_channel = -1; + ret = disable_hwvad(dev, true); + } + spin_unlock_irqrestore(&micfil->hwvad_lock, flags); + + if (ret) { + dev_err(dev, "Failed to %s hwvad: %d\n", + vad_channel <= 7 ? "enable" : "disable", ret); + return ret; + } + + return count; +} + +static struct kobj_attribute hwvad_en_attr = __ATTR(enable, + 0660, + NULL, + micfil_hwvad_handler); + static int fsl_micfil_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -668,6 +2220,26 @@ static int fsl_micfil_probe(struct platform_device *pdev) return PTR_ERR(micfil->mclk); } + micfil->busclk = devm_clk_get(&pdev->dev, "ipg_clk"); + if (IS_ERR(micfil->busclk)) { + dev_err(&pdev->dev, "failed to get ipg clock: %ld\n", + PTR_ERR(micfil->busclk)); + return PTR_ERR(micfil->busclk); + } + + /* get audio pll1 and pll2 */ + micfil->clk_src[MICFIL_AUDIO_PLL1] = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(micfil->clk_src[MICFIL_AUDIO_PLL1])) + micfil->clk_src[MICFIL_AUDIO_PLL1] = NULL; + + micfil->clk_src[MICFIL_AUDIO_PLL2] = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(micfil->clk_src[MICFIL_AUDIO_PLL2])) + micfil->clk_src[MICFIL_AUDIO_PLL2] = NULL; + + micfil->clk_src[MICFIL_CLK_EXT3] = devm_clk_get(&pdev->dev, "clkext3"); + if (IS_ERR(micfil->clk_src[MICFIL_CLK_EXT3])) + micfil->clk_src[MICFIL_CLK_EXT3] = NULL; + /* init regmap */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(&pdev->dev, res); @@ -675,7 +2247,7 @@ static int fsl_micfil_probe(struct platform_device *pdev) return PTR_ERR(regs); micfil->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "ipg_clk", + NULL, regs, &fsl_micfil_regmap_config); if (IS_ERR(micfil->regmap)) { @@ -711,6 +2283,30 @@ static int fsl_micfil_probe(struct platform_device *pdev) if (of_property_read_bool(np, "fsl,shared-interrupt")) irqflag = IRQF_SHARED; + /* Digital Microphone interface voice activity detector event + * interrupt - IRQ 44 + */ + ret = devm_request_threaded_irq(&pdev->dev, micfil->irq[2], + hwvad_isr, voice_detected_fn, + irqflag, micfil->name, micfil); + if (ret) { + dev_err(&pdev->dev, "failed to claim hwvad event irq %u\n", + micfil->irq[0]); + return ret; + } + + /* Digital Microphone interface voice activity detector error + * interrupt - IRQ 45 + */ + ret = devm_request_irq(&pdev->dev, micfil->irq[3], + hwvad_err_isr, irqflag, + micfil->name, micfil); + if (ret) { + dev_err(&pdev->dev, "failed to claim hwvad error irq %u\n", + micfil->irq[1]); + return ret; + } + /* Digital Microphone interface interrupt - IRQ 109 */ ret = devm_request_irq(&pdev->dev, micfil->irq[0], micfil_isr, irqflag, @@ -731,14 +2327,23 @@ static int fsl_micfil_probe(struct platform_device *pdev) return ret; } + micfil->slave_mode = false; + micfil->dma_params_rx.chan_name = "rx"; micfil->dma_params_rx.addr = res->start + REG_MICFIL_DATACH0; micfil->dma_params_rx.maxburst = MICFIL_DMA_MAXBURST_RX; + /* set default rate to first value in available vad rates */ + micfil->vad_rate_index = 0; + /* init HWVAD enable/disable spinlock */ + spin_lock_init(&micfil->hwvad_lock); platform_set_drvdata(pdev, micfil); pm_runtime_enable(&pdev->dev); + regcache_cache_only(micfil->regmap, true); + + fsl_micfil_dai.capture.formats = micfil->soc->formats; ret = devm_snd_soc_register_component(&pdev->dev, &fsl_micfil_component, &fsl_micfil_dai, 1); @@ -748,21 +2353,50 @@ static int fsl_micfil_probe(struct platform_device *pdev) return ret; } - ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) + if (micfil->soc->imx) + ret = imx_pcm_platform_register(&pdev->dev); + else + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + + if (ret) { dev_err(&pdev->dev, "failed to pcm register\n"); + return ret; + } - return ret; + /* create sysfs entry used to enable hwvad from userspace */ + micfil->hwvad_kobject = kobject_create_and_add("hwvad", + &pdev->dev.kobj); + if (!micfil->hwvad_kobject) + return -ENOMEM; + + ret = sysfs_create_file(micfil->hwvad_kobject, + &hwvad_en_attr.attr); + if (ret) { + dev_err(&pdev->dev, "failed to create file for hwvad_enable\n"); + kobject_put(micfil->hwvad_kobject); + return -ENOMEM; + } + + return 0; } #ifdef CONFIG_PM static int __maybe_unused fsl_micfil_runtime_suspend(struct device *dev) { struct fsl_micfil *micfil = dev_get_drvdata(dev); + u32 state; + + state = atomic_read(&micfil->hwvad_state); + if (state == MICFIL_HWVAD_ON) + return 0; regcache_cache_only(micfil->regmap, true); - clk_disable_unprepare(micfil->mclk); + /* Disable the clock only if the hwvad is not enabled */ + if (state == MICFIL_HWVAD_OFF) + clk_disable_unprepare(micfil->mclk); + + clk_disable_unprepare(micfil->busclk); return 0; } @@ -771,6 +2405,21 @@ static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev) { struct fsl_micfil *micfil = dev_get_drvdata(dev); int ret; + u32 state; + + ret = clk_prepare_enable(micfil->busclk); + if (ret < 0) + return ret; + + state = atomic_read(&micfil->hwvad_state); + + /* enable mclk only if the hwvad is not enabled + * When hwvad is enabled, clock won't be disabled + * in suspend since hwvad and recording share the + * same clock + */ + if (state == MICFIL_HWVAD_ON) + return 0; ret = clk_prepare_enable(micfil->mclk); if (ret < 0) @@ -787,6 +2436,19 @@ static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev) #ifdef CONFIG_PM_SLEEP static int __maybe_unused fsl_micfil_suspend(struct device *dev) { + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 state; + + state = atomic_read(&micfil->hwvad_state); + + if (state == MICFIL_HWVAD_ON) { + dev_err(dev, "Disabling hwvad on suspend"); + ret = disable_hwvad(dev, false); + if (ret) + dev_warn(dev, "Failed to disable hwvad"); + } + pm_runtime_force_suspend(dev); return 0; @@ -794,8 +2456,20 @@ static int __maybe_unused fsl_micfil_suspend(struct device *dev) static int __maybe_unused fsl_micfil_resume(struct device *dev) { + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 state; + pm_runtime_force_resume(dev); + state = atomic_read(&micfil->hwvad_state); + if (state == MICFIL_HWVAD_ON) { + dev_err(dev, "Enabling hwvad on resume"); + ret = enable_hwvad(dev, false); + if (ret) + dev_warn(dev, "Failed to re-enable hwvad"); + } + return 0; } #endif /* CONFIG_PM_SLEEP */ diff --git a/sound/soc/fsl/fsl_micfil.h b/sound/soc/fsl/fsl_micfil.h index bac825c3135a..14ad08b6b150 100644 --- a/sound/soc/fsl/fsl_micfil.h +++ b/sound/soc/fsl/fsl_micfil.h @@ -258,6 +258,40 @@ #define MICFIL_VAD0_STAT_IF_MASK BIT(MICFIL_VAD0_STAT_IF_SHIFT) #define MICFIL_VAD0_STAT_IF BIT(MICFIL_VAD0_STAT_IF_SHIFT) +/* HWVAD Constants */ +#define MICFIL_HWVAD_ENVELOPE_MODE 0 +#define MICFIL_HWVAD_ENERGY_MODE 1 +#define MICFIL_HWVAD_INIT_FRAMES 10 +#define MICFIL_HWVAD_INPGAIN 0 +#define MICFIL_HWVAD_SGAIN 6 +#define MICFIL_HWVAD_NGAIN 3 +#define MICFIL_HWVAD_NFILADJ 0 +#define MICFIL_HWVAD_ZCDADJ (1 << (MICFIL_VAD0_ZCD_ZCDADJ_WIDTH - 2)) +#define MICFIL_HWVAD_ZCDTH 10 /* initial threshold value */ +#define MICFIL_HWVAD_ZCDOR 0 +#define MICFIL_HWVAD_ZCDAND 1 +#define MICFIL_HWVAD_ZCD_MANUAL 0 +#define MICFIL_HWVAD_ZCD_AUTO 1 +#define MICFIL_HWVAD_HPF_BYPASS 0 +#define MICFIL_HWVAD_HPF_1750HZ 1 +#define MICFIL_HWVAD_HPF_215HZ 2 +#define MICFIL_HWVAD_HPF_102HZ 3 +#define MICFIL_HWVAD_FRAMET_DEFAULT 10 + +/* MICFIL DC Remover Control Register -- REG_MICFIL_DC_CTRL */ +#define MICFIL_DC_CTRL_SHIFT 0 +#define MICFIL_DC_CTRL_MASK 0xFFFF +#define MICFIL_DC_CTRL_WIDTH 2 +#define MICFIL_DC_CHX_SHIFT(v) (2 * (v)) +#define MICFIL_DC_CHX_MASK(v) ((BIT(MICFIL_DC_CTRL_WIDTH) - 1) \ + << MICFIL_DC_CHX_SHIFT(v)) +#define MICFIL_DC_MODE(v1, v2) (((v1) << MICFIL_DC_CHX_SHIFT(v2)) \ + & MICFIL_DC_CHX_MASK(v2)) +#define MICFIL_DC_CUTOFF_21HZ 0 +#define MICFIL_DC_CUTOFF_83HZ 1 +#define MICFIL_DC_CUTOFF_152Hz 2 +#define MICFIL_DC_BYPASS 3 + /* MICFIL Output Control Register */ #define MICFIL_OUTGAIN_CHX_SHIFT(v) (4 * (v)) @@ -273,11 +307,24 @@ #define FIFO_PTRWID 3 #define FIFO_LEN BIT(FIFO_PTRWID) -#define MICFIL_IRQ_LINES 2 +#define MICFIL_IRQ_LINES 4 #define MICFIL_MAX_RETRY 25 -#define MICFIL_SLEEP_MIN 90000 /* in us */ -#define MICFIL_SLEEP_MAX 100000 /* in us */ +#define MICFIL_SLEEP 100 /* in ms */ #define MICFIL_DMA_MAXBURST_RX 6 #define MICFIL_CTRL2_OSR_DEFAULT (0 << MICFIL_CTRL2_CICOSR_SHIFT) +#define MICFIL_DEFAULT_RATE 48000 +#define MICFIL_CLK_SRC_NUM 3 +#define MICFIL_CLK_AUTO 0 + +/* clock source ids */ +#define MICFIL_AUDIO_PLL1 0 +#define MICFIL_AUDIO_PLL2 1 +#define MICFIL_CLK_EXT3 2 + +/* States of micfil */ +#define MICFIL_HWVAD_OFF 0 +#define MICFIL_HWVAD_ON 1 +#define MICFIL_RECORDING_OFF 0 +#define MICFIL_RECORDING_ON 1 #endif /* _FSL_MICFIL_H */ diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.c b/sound/soc/fsl/fsl_rpmsg_i2s.c new file mode 100644 index 000000000000..87f03f5f16fe --- /dev/null +++ b/sound/soc/fsl/fsl_rpmsg_i2s.c @@ -0,0 +1,447 @@ +/* + * Freescale ALSA SoC rpmsg i2s driver. + * + * Copyright 2017 NXP + * + * 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. + * + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/rpmsg.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_rpmsg_i2s.h" +#include "imx-pcm.h" + +#define FSL_RPMSG_I2S_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\ + SNDRV_PCM_RATE_48000) +#define FSL_RPMSG_I2S_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static int i2s_send_message(struct i2s_rpmsg *msg, + struct i2s_info *info) +{ + int err = 0; + + mutex_lock(&info->tx_lock); + if (!info->rpdev) { + dev_err(info->dev, "rpmsg channel not ready, m4 image ready?\n"); + mutex_unlock(&info->tx_lock); + return -EINVAL; + } + + dev_dbg(&info->rpdev->dev, "send cmd %d\n", msg->send_msg.header.cmd); + + if (!(msg->send_msg.header.type == I2S_TYPE_C)) + reinit_completion(&info->cmd_complete); + + err = rpmsg_send(info->rpdev->ept, (void *)&msg->send_msg, + sizeof(struct i2s_rpmsg_s)); + if (err) { + dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err); + mutex_unlock(&info->tx_lock); + return err; + } + + if (!(msg->send_msg.header.type == I2S_TYPE_C)) { + /* wait response from rpmsg */ + err = wait_for_completion_timeout(&info->cmd_complete, + msecs_to_jiffies(RPMSG_TIMEOUT)); + if (!err) { + dev_err(&info->rpdev->dev, + "rpmsg_send cmd %d timeout!\n", + msg->send_msg.header.cmd); + mutex_unlock(&info->tx_lock); + return -ETIMEDOUT; + } + memcpy(&msg->recv_msg, &info->recv_msg, + sizeof(struct i2s_rpmsg_r)); + memcpy(&info->rpmsg[msg->recv_msg.header.cmd].recv_msg, + &msg->recv_msg, sizeof(struct i2s_rpmsg_r)); + + /* + * Reset the buffer pointer to be zero, actully we have + * set the buffer pointer to be zero in imx_rpmsg_terminate_all + * But if there is timer task queued in queue, after it is + * executed the buffer pointer will be changed, so need to + * reset it again with TERMINATE command. + */ + + switch (msg->send_msg.header.cmd) { + case I2S_TX_TERMINATE: + info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0; + break; + case I2S_RX_TERMINATE: + info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0; + break; + default: + break; + } + } + + dev_dbg(&info->rpdev->dev, "cmd:%d, resp %d\n", + msg->send_msg.header.cmd, + info->recv_msg.param.resp); + mutex_unlock(&info->tx_lock); + + return 0; +} + +static const unsigned int fsl_rpmsg_rates[] = { + 8000, 11025, 16000, 22050, 44100, + 32000, 48000, 96000, 88200, 176400, 192000, + 352800, 384000, 705600, 768000, 1411200, 2822400, +}; + +static const struct snd_pcm_hw_constraint_list fsl_rpmsg_rate_constraints = { + .count = ARRAY_SIZE(fsl_rpmsg_rates), + .list = fsl_rpmsg_rates, +}; + +static int fsl_rpmsg_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &fsl_rpmsg_rate_constraints); + + return ret; +} + +static const struct snd_soc_dai_ops fsl_rpmsg_dai_ops = { + .startup = fsl_rpmsg_startup, +}; + +static struct snd_soc_dai_driver fsl_rpmsg_i2s_dai = { + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_RPMSG_I2S_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_RPMSG_I2S_FORMATS, + }, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, + .ops = &fsl_rpmsg_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_component = { + .name = "fsl-rpmsg-i2s", +}; + +static const struct of_device_id fsl_rpmsg_i2s_ids[] = { + { .compatible = "fsl,imx7ulp-rpmsg-i2s"}, + { .compatible = "fsl,imx8mq-rpmsg-i2s"}, + { .compatible = "fsl,imx8qxp-rpmsg-i2s"}, + { .compatible = "fsl,imx8qm-rpmsg-i2s"}, + { .compatible = "fsl,imx8mn-rpmsg-i2s"}, + { .compatible = "fsl,imx8mp-rpmsg-i2s"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_rpmsg_i2s_ids); + +static void rpmsg_i2s_work(struct work_struct *work) +{ + struct work_of_rpmsg *work_of_rpmsg; + struct i2s_info *i2s_info; + bool is_period_done = false; + unsigned long flags; + struct i2s_rpmsg msg; + + work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); + i2s_info = work_of_rpmsg->i2s_info; + + spin_lock_irqsave(&i2s_info->lock[0], flags); + if (i2s_info->period_done_msg_enabled[0]) { + memcpy(&msg, &i2s_info->period_done_msg[0], sizeof(struct i2s_rpmsg_s)); + i2s_info->period_done_msg_enabled[0] = false; + spin_unlock_irqrestore(&i2s_info->lock[0], flags); + + i2s_send_message(&msg, i2s_info); + } else + spin_unlock_irqrestore(&i2s_info->lock[0], flags); + + if (i2s_info->period_done_msg_enabled[1]) { + i2s_send_message(&i2s_info->period_done_msg[1], i2s_info); + i2s_info->period_done_msg_enabled[1] = false; + } + + if (work_of_rpmsg->msg.send_msg.header.type == I2S_TYPE_C && + (work_of_rpmsg->msg.send_msg.header.cmd == I2S_TX_PERIOD_DONE || + work_of_rpmsg->msg.send_msg.header.cmd == I2S_RX_PERIOD_DONE)) + is_period_done = true; + + if (!is_period_done) + i2s_send_message(&work_of_rpmsg->msg, i2s_info); + + spin_lock_irqsave(&i2s_info->wq_lock, flags); + i2s_info->work_read_index++; + i2s_info->work_read_index %= WORK_MAX_NUM; + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); +} + +static int fsl_rpmsg_i2s_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_rpmsg_i2s *rpmsg_i2s; + struct i2s_info *i2s_info; + int audioindex = 0; + int ret; + int i; + + rpmsg_i2s = devm_kzalloc(&pdev->dev, sizeof(struct fsl_rpmsg_i2s), + GFP_KERNEL); + if (!rpmsg_i2s) + return -ENOMEM; + + rpmsg_i2s->pdev = pdev; + i2s_info = &rpmsg_i2s->i2s_info; + + ret = of_property_read_u32(np, "fsl,audioindex", &audioindex); + if (ret) + audioindex = 0; + + /* Setup work queue */ + i2s_info->rpmsg_wq = alloc_ordered_workqueue("rpmsg_i2s", WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE); + if (i2s_info->rpmsg_wq == NULL) { + dev_err(&pdev->dev, "workqueue create failed\n"); + return -ENOMEM; + } + + i2s_info->work_write_index = 1; + i2s_info->send_message = i2s_send_message; + + for (i = 0; i < WORK_MAX_NUM; i++) { + INIT_WORK(&i2s_info->work_list[i].work, rpmsg_i2s_work); + i2s_info->work_list[i].i2s_info = i2s_info; + } + + for (i = 0; i < I2S_CMD_MAX_NUM ; i++) { + i2s_info->rpmsg[i].send_msg.header.cate = IMX_RPMSG_AUDIO; + i2s_info->rpmsg[i].send_msg.header.major = IMX_RMPSG_MAJOR; + i2s_info->rpmsg[i].send_msg.header.minor = IMX_RMPSG_MINOR; + i2s_info->rpmsg[i].send_msg.header.type = I2S_TYPE_A; + i2s_info->rpmsg[i].send_msg.param.audioindex = audioindex; + } + + mutex_init(&i2s_info->tx_lock); + mutex_init(&i2s_info->i2c_lock); + spin_lock_init(&i2s_info->lock[0]); + spin_lock_init(&i2s_info->lock[1]); + spin_lock_init(&i2s_info->wq_lock); + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx7ulp-rpmsg-i2s")) { + rpmsg_i2s->codec_wm8960 = 1; + rpmsg_i2s->version = 1; + rpmsg_i2s->rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000; + rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE; + fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats; + fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats; + } + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx8qxp-rpmsg-i2s")) { + rpmsg_i2s->codec_wm8960 = 1 + (1 << 16); + rpmsg_i2s->version = 1; + rpmsg_i2s->codec_cs42888 = 1 + (2 << 16); + } + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx8qm-rpmsg-i2s")) { + rpmsg_i2s->codec_wm8960 = 0; + rpmsg_i2s->version = 1; + rpmsg_i2s->codec_cs42888 = 1 + (0 << 16); + } + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx8mq-rpmsg-i2s")) { + rpmsg_i2s->codec_dummy = 0; + rpmsg_i2s->codec_ak4497 = 1; + rpmsg_i2s->version = 2; + rpmsg_i2s->rates = SNDRV_PCM_RATE_KNOT; + rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_DSD_U8 | + SNDRV_PCM_FMTBIT_DSD_U16_LE | + SNDRV_PCM_FMTBIT_DSD_U32_LE; + + fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats; + fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats; + } + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx8mn-rpmsg-i2s")) { + rpmsg_i2s->codec_dummy = 1; + rpmsg_i2s->version = 2; + rpmsg_i2s->rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000; + rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE; + + fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats; + fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats; + } + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx8mp-rpmsg-i2s")) { + rpmsg_i2s->codec_wm8960 = 1; + rpmsg_i2s->codec_in_dt = 1; + rpmsg_i2s->version = 2; + rpmsg_i2s->rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000; + rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE; + + fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats; + fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates; + fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats; + } + + if (of_property_read_bool(pdev->dev.of_node, "fsl,enable-lpa")) + rpmsg_i2s->enable_lpa = 1; + + if (of_property_read_u32(np, "fsl,dma-buffer-size", + &i2s_info->prealloc_buffer_size)) + i2s_info->prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE; + + platform_set_drvdata(pdev, rpmsg_i2s); + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, + &fsl_rpmsg_i2s_dai, 1); + if (ret) + return ret; + + return imx_rpmsg_platform_register(&pdev->dev); +} + +static int fsl_rpmsg_i2s_remove(struct platform_device *pdev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = platform_get_drvdata(pdev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + + if (i2s_info->rpmsg_wq) + destroy_workqueue(i2s_info->rpmsg_wq); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_rpmsg_i2s_runtime_resume(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + + pm_qos_add_request(&rpmsg_i2s->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, 0); + return 0; +} + +static int fsl_rpmsg_i2s_runtime_suspend(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + + pm_qos_remove_request(&rpmsg_i2s->pm_qos_req); + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int fsl_rpmsg_i2s_suspend(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg_tx; + struct i2s_rpmsg *rpmsg_rx; + + rpmsg_tx = &i2s_info->rpmsg[I2S_TX_SUSPEND]; + rpmsg_rx = &i2s_info->rpmsg[I2S_RX_SUSPEND]; + + rpmsg_tx->send_msg.header.cmd = I2S_TX_SUSPEND; + i2s_send_message(rpmsg_tx, i2s_info); + + rpmsg_rx->send_msg.header.cmd = I2S_RX_SUSPEND; + i2s_send_message(rpmsg_rx, i2s_info); + + return 0; +} + +static int fsl_rpmsg_i2s_resume(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg_tx; + struct i2s_rpmsg *rpmsg_rx; + + rpmsg_tx = &i2s_info->rpmsg[I2S_TX_RESUME]; + rpmsg_rx = &i2s_info->rpmsg[I2S_RX_RESUME]; + + rpmsg_tx->send_msg.header.cmd = I2S_TX_RESUME; + i2s_send_message(rpmsg_tx, i2s_info); + + rpmsg_rx->send_msg.header.cmd = I2S_RX_RESUME; + i2s_send_message(rpmsg_rx, i2s_info); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_rpmsg_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_rpmsg_i2s_runtime_suspend, + fsl_rpmsg_i2s_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_rpmsg_i2s_suspend, fsl_rpmsg_i2s_resume) +}; + +static struct platform_driver fsl_rpmsg_i2s_driver = { + .probe = fsl_rpmsg_i2s_probe, + .remove = fsl_rpmsg_i2s_remove, + .driver = { + .name = "fsl-rpmsg-i2s", + .pm = &fsl_rpmsg_i2s_pm_ops, + .of_match_table = fsl_rpmsg_i2s_ids, + }, +}; + +module_platform_driver(fsl_rpmsg_i2s_driver); + +MODULE_DESCRIPTION("Freescale Soc rpmsg_i2s Interface"); +MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@freescale.com>"); +MODULE_ALIAS("platform:fsl-rpmsg_i2s"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.h b/sound/soc/fsl/fsl_rpmsg_i2s.h new file mode 100644 index 000000000000..dab7aeab3bcd --- /dev/null +++ b/sound/soc/fsl/fsl_rpmsg_i2s.h @@ -0,0 +1,455 @@ +/* + * Copyright 2017 NXP + * + * 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. + * + ****************************************************************************** + * communication stack of audio with rpmsg + ****************************************************************************** + * Packet structure: + * A SRTM message consists of a 10 bytes header followed by 0~N bytes of data + * + * +---------------+-------------------------------+ + * | | Content | + * +---------------+-------------------------------+ + * | Byte Offset | 7 6 5 4 3 2 1 0 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 0 | Category | + * +---------------+---+---+---+---+---+---+---+---+ + * | 1 ~ 2 | Version | + * +---------------+---+---+---+---+---+---+---+---+ + * | 3 | Type | + * +---------------+---+---+---+---+---+---+---+---+ + * | 4 | Command | + * +---------------+---+---+---+---+---+---+---+---+ + * | 5 | Reserved0 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 6 | Reserved1 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 7 | Reserved2 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 8 | Reserved3 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 9 | Reserved4 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 10 | DATA 0 | + * +---------------+---+---+---+---+---+---+---+---+ + * : : : : : : : : : : : : : + * +---------------+---+---+---+---+---+---+---+---+ + * | N + 10 - 1 | DATA N-1 | + * +---------------+---+---+---+---+---+---+---+---+ + * + * +----------+------------+------------------------------------------------+ + * | Field | Byte | | + * +----------+------------+------------------------------------------------+ + * | Category | 0 | The destination category. | + * +----------+------------+------------------------------------------------+ + * | Version | 1 ~ 2 | The category version of the sender of the | + * | | | packet. | + * | | | The first byte represent the major version of | + * | | | the packet.The second byte represent the minor | + * | | | version of the packet. | + * +----------+------------+------------------------------------------------+ + * | Type | 3 | The message type of current message packet. | + * +----------+------------+------------------------------------------------+ + * | Command | 4 | The command byte sent to remote processor/SoC. | + * +----------+------------+------------------------------------------------+ + * | Reserved | 5 ~ 9 | Reserved field for future extension. | + * +----------+------------+------------------------------------------------+ + * | Data | N | The data payload of the message packet. | + * +----------+------------+------------------------------------------------+ + * + * Audio control: + * SRTM Audio Control Category Request Command Table: + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | Category | Version | Type | Command | Data | Function | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x00 | Data[0]: Audio Device Index | Open an Audio TX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x01 | Data[0]: Audio Device Index | Start an Audio TX Instance. | + * | | | | | Same as above command | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x02 | Data[0]: Audio Device Index | Pause an Audio TX Instance. | + * | | | | | Same as above command | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x03 | Data[0]: Audio Device Index | Resume an Audio TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x04 | Data[0]: Audio Device Index | Terminate an Audio TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x05 | Data[0]: Audio Device Index | Close an Audio TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x06 | Data[0]: Audio Device Index | Set Parameters for an Audio TX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x07 | Data[0]: Audio Device Index | Set Audio TX Buffer. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x08 | Data[0]: Audio Device Index | Suspend an Audio TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x09 | Data[0]: Audio Device Index | Resume an Audio TX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0A | Data[0]: Audio Device Index | Open an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0B | Data[0]: Audio Device Index | Start an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0C | Data[0]: Audio Device Index | Pause an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0D | Data[0]: Audio Device Index | Resume an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0E | Data[0]: Audio Device Index | Terminate an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0F | Data[0]: Audio Device Index | Close an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x10 | Data[0]: Audio Device Index | Set Parameters for an Audio RX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x11 | Data[0]: Audio Device Index | Set Audio RX Buffer. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x12 | Data[0]: Audio Device Index | Suspend an Audio RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x13 | Data[0]: Audio Device Index | Resume an Audio RX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x14 | Data[0]: Audio Device Index | Set register value to codec. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-14]: value | | + * | | | | | Data[15-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x15 | Data[0]: Audio Device Index | Get register value from codec. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * Note 1: See <List of Sample Format> for available value of + * Sample Format; + * Note 2: See <List of Audio Channels> for available value of Channels; + * Note 3: Sample Rate of Set Parameters for an Audio TX Instance + * Command and Set Parameters for an Audio RX Instance Command is + * in little-endian format. + * + * SRTM Audio Control Category Response Command Table: + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | Category | Version | Type | Command | Data | Function | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x00 | Data[0]: Audio Device Index | Reply for Open an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x01 | Data[0]: Audio Device Index | Reply for Start an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x02 | Data[0]: Audio Device Index | Reply for Pause an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x03 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x04 | Data[0]: Audio Device Index | Reply for Terminate an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x05 | Data[0]: Audio Device Index | Reply for Close an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x06 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio | + * | | | | | Data[1]: Return code | TX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x07 | Data[0]: Audio Device Index | Reply for Set Audio TX Buffer. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x08 | Data[0]: Audio Device Index | Reply for Suspend an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x09 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0A | Data[0]: Audio Device Index | Reply for Open an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0B | Data[0]: Audio Device Index | Reply for Start an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0C | Data[0]: Audio Device Index | Reply for Pause an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0D | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0E | Data[0]: Audio Device Index | Reply for Terminate an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0F | Data[0]: Audio Device Index | Reply for Close an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x10 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio | + * | | | | | Data[1]: Return code | RX Instance. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x11 | Data[0]: Audio Device Index | Reply for Set Audio RX Buffer. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x12 | Data[0]: Audio Device Index | Reply for Supend an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x13 | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x14 | Data[0]: Audio Device Index | Reply for Set codec register value. | + * | | | | | Data[1]: Return code | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x15 | Data[0]: Audio Device Index | Reply for Get codec register value. | + * | | | | | Data[1]: Return code | | + * | | | | | Data[2-6]: reserved | | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-14]: value | | + * | | | | | Data[15-22]: reserved | | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * + * SRTM Audio Control Category Notification Command Table: + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | Category | Version | Type | Command | Data | Function | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x02 | 0x00 | Data[0]: Audio Device Index | Notify one TX period is finished. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * | 0x03 | 0x0100 | 0x02 | 0x01 | Data[0]: Audio Device Index | Notify one RX period is finished. | + * +------------+---------+------+---------+-------------------------------+------------------------------------------------+ + * + * List of Sample Format: + * +--------------------+----------------------------------------------+ + * | Sample Format | Description | + * +--------------------+----------------------------------------------+ + * | 0x0 | S16_LE | + * +--------------------+----------------------------------------------+ + * | 0x1 | S24_LE | + * +--------------------+----------------------------------------------+ + * + * List of Audio Channels + * +--------------------+----------------------------------------------+ + * | Audio Channel | Description | + * +--------------------+----------------------------------------------+ + * | 0x0 | Left Channel | + * +--------------------+----------------------------------------------+ + * | 0x1 | Right Channel | + * +--------------------+----------------------------------------------+ + * | 0x2 | Left & Right Channel | + * +--------------------+----------------------------------------------+ + * + */ + +#ifndef __FSL_RPMSG_I2S_H +#define __FSL_RPMSG_I2S_H + +#include <linux/pm_qos.h> +#include <linux/imx_rpmsg.h> +#include <linux/interrupt.h> +#include <sound/dmaengine_pcm.h> + +#define RPMSG_TIMEOUT 1000 + +#define I2S_TX_OPEN 0x0 +#define I2S_TX_START 0x1 +#define I2S_TX_PAUSE 0x2 +#define I2S_TX_RESTART 0x3 +#define I2S_TX_TERMINATE 0x4 +#define I2S_TX_CLOSE 0x5 +#define I2S_TX_HW_PARAM 0x6 +#define I2S_TX_BUFFER 0x7 +#define I2S_TX_SUSPEND 0x8 +#define I2S_TX_RESUME 0x9 + +#define I2S_RX_OPEN 0xA +#define I2S_RX_START 0xB +#define I2S_RX_PAUSE 0xC +#define I2S_RX_RESTART 0xD +#define I2S_RX_TERMINATE 0xE +#define I2S_RX_CLOSE 0xF +#define I2S_RX_HW_PARAM 0x10 +#define I2S_RX_BUFFER 0x11 +#define I2S_RX_SUSPEND 0x12 +#define I2S_RX_RESUME 0x13 +#define SET_CODEC_VALUE 0x14 +#define GET_CODEC_VALUE 0x15 +#define I2S_TX_POINTER 0x16 +#define I2S_RX_POINTER 0x17 + +#define I2S_TYPE_A_NUM 0x18 + +#define WORK_MAX_NUM 0x30 + +#define I2S_TX_PERIOD_DONE 0x0 +#define I2S_RX_PERIOD_DONE 0x1 + +#define I2S_TYPE_C_NUM 0x2 + +#define I2S_CMD_MAX_NUM (I2S_TYPE_A_NUM + I2S_TYPE_C_NUM) + +#define I2S_TYPE_A 0x0 +#define I2S_TYPE_B 0x1 +#define I2S_TYPE_C 0x2 + +#define I2S_RESP_NONE 0x0 +#define I2S_RESP_NOT_ALLOWED 0x1 +#define I2S_RESP_SUCCESS 0x2 +#define I2S_RESP_FAILED 0x3 + +#define RPMSG_S16_LE 0x0 +#define RPMSG_S24_LE 0x1 +#define RPMSG_S32_LE 0x2 +#define RPMSG_DSD_U16_LE 0x3 +#define RPMSG_DSD_U24_LE 0x4 +#define RPMSG_DSD_U32_LE 0x5 + +#define RPMSG_CH_LEFT 0x0 +#define RPMSG_CH_RIGHT 0x1 +#define RPMSG_CH_STEREO 0x2 + +struct i2s_param_s { + unsigned char audioindex; + unsigned char format; + unsigned char channels; + unsigned int rate; + unsigned int buffer_addr; /* Register for SET_CODEC_VALUE*/ + unsigned int buffer_size; /* register value for SET_CODEC_VALUE*/ + unsigned int period_size; + unsigned int buffer_tail; +} __packed; + +struct i2s_param_r { + unsigned char audioindex; + unsigned char resp; + unsigned char reserved1[1]; + unsigned int buffer_offset; /* the consumed offset of buffer*/ + unsigned int reg_addr; + unsigned int reg_data; + unsigned char reserved2[4]; + unsigned int buffer_tail; +} __packed; + +/* struct of send message */ +struct i2s_rpmsg_s { + struct imx_rpmsg_head header; + struct i2s_param_s param; +}; + +/* struct of received message */ +struct i2s_rpmsg_r { + struct imx_rpmsg_head header; + struct i2s_param_r param; +}; + +struct i2s_rpmsg { + struct i2s_rpmsg_s send_msg; + struct i2s_rpmsg_r recv_msg; +}; + +struct work_of_rpmsg { + struct i2s_info *i2s_info; + /* sent msg for each work */ + struct i2s_rpmsg msg; + struct work_struct work; +}; + +struct stream_timer { + struct timer_list timer; + struct snd_pcm_substream *substream; +}; + +typedef void (*dma_callback)(void *arg); +struct i2s_info { + struct rpmsg_device *rpdev; + struct device *dev; + struct completion cmd_complete; + + /* received msg (global) */ + struct i2s_rpmsg_r recv_msg; + + struct i2s_rpmsg rpmsg[I2S_CMD_MAX_NUM]; + struct i2s_rpmsg period_done_msg[2]; + bool period_done_msg_enabled[2]; + + struct workqueue_struct *rpmsg_wq; + struct work_of_rpmsg work_list[WORK_MAX_NUM]; + int work_write_index; + int work_read_index; + int msg_drop_count[2]; + int num_period[2]; + void *callback_param[2]; + int (*send_message)(struct i2s_rpmsg *msg, struct i2s_info *info); + dma_callback callback[2]; + spinlock_t lock[2]; + spinlock_t wq_lock; + struct mutex tx_lock; + struct mutex i2c_lock; + struct stream_timer stream_timer[2]; + int prealloc_buffer_size; +}; + +struct fsl_rpmsg_i2s { + struct platform_device *pdev; + struct i2s_info i2s_info; + struct pm_qos_request pm_qos_req; + int codec_dummy; + int codec_wm8960; + int codec_cs42888; + int codec_ak4497; + int codec_in_dt; + int force_lpa; + int version; + int rates; + u64 formats; + int enable_lpa; +}; + +#define RPMSG_CODEC_DRV_NAME_WM8960 "rpmsg-audio-codec-wm8960" +#define RPMSG_CODEC_DRV_NAME_CS42888 "rpmsg-audio-codec-cs42888" +#define RPMSG_CODEC_DRV_NAME_AK4497 "rpmsg-audio-codec-ak4497" + +struct fsl_rpmsg_codec { + int audioindex; + + /*property for wm8960*/ + bool capless; + bool shared_lrclk; + + /*property for cs42xx8*/ + + char name[32]; + int num_adcs; +}; + +#endif /* __FSL_RPMSG_I2S_H */ diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 027259695551..d9958a52e871 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -2,34 +2,102 @@ // // Freescale ALSA SoC Digital Audio Interface (SAI) driver. // -// Copyright 2012-2015 Freescale Semiconductor, Inc. +// Copyright 2012-2016 Freescale Semiconductor, Inc. #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/delay.h> #include <linux/dmaengine.h> #include <linux/module.h> -#include <linux/of_address.h> #include <linux/of_device.h> +#include <linux/of_address.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/time.h> +#include <linux/pm_qos.h> #include <sound/core.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> #include <linux/mfd/syscon.h> #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/pm_runtime.h> +#include <linux/busfreq-imx.h> +#include "fsl_dsd.h" #include "fsl_sai.h" #include "imx-pcm.h" #define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\ FSL_SAI_CSR_FEIE) +#define FSL_SAI_VERID_0301 0x0301 + +static struct fsl_sai_soc_data fsl_sai_vf610 = { + .imx = false, + /*dataline is mask, not index*/ + .dataline = 0x1, + .fifos = 1, + .fifo_depth = 32, + .flags = 0, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx6sx = { + .imx = true, + .dataline = 0x1, + .fifos = 1, + .fifo_depth = 32, + .flags = 0, + .reg_offset = 0, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx6ul = { + .imx = true, + .dataline = 0x1, + .fifos = 1, + .fifo_depth = 32, + .flags = 0, + .reg_offset = 0, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx7ulp = { + .imx = true, + .dataline = 0x3, + .fifos = 2, + .fifo_depth = 16, + .flags = SAI_FLAG_PMQOS, + .reg_offset = 8, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx8mq = { + .imx = true, + .dataline = 0xff, + .fifos = 8, + .fifo_depth = 128, + .flags = 0, + .reg_offset = 8, + .constrain_period_size = false, +}; + +static struct fsl_sai_soc_data fsl_sai_imx8qm = { + .imx = true, + .dataline = 0xf, + .fifos = 1, + .fifo_depth = 64, + .flags = 0, + .reg_offset = 0, + .constrain_period_size = true, +}; + static const unsigned int fsl_sai_rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, - 88200, 96000, 176400, 192000 + 88200, 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, 2822400, }; static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = { @@ -40,7 +108,7 @@ static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = { static irqreturn_t fsl_sai_isr(int irq, void *devid) { struct fsl_sai *sai = (struct fsl_sai *)devid; - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; struct device *dev = &sai->pdev->dev; u32 flags, xcsr, mask; bool irq_none = true; @@ -53,7 +121,7 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT; /* Tx IRQ */ - regmap_read(sai->regmap, FSL_SAI_TCSR(ofs), &xcsr); + regmap_read(sai->regmap, FSL_SAI_TCSR(offset), &xcsr); flags = xcsr & mask; if (flags) @@ -83,11 +151,11 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) xcsr &= ~FSL_SAI_CSR_xF_MASK; if (flags) - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), flags | xcsr); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), flags | xcsr); irq_rx: /* Rx IRQ */ - regmap_read(sai->regmap, FSL_SAI_RCSR(ofs), &xcsr); + regmap_read(sai->regmap, FSL_SAI_RCSR(offset), &xcsr); flags = xcsr & mask; if (flags) @@ -117,7 +185,7 @@ irq_rx: xcsr &= ~FSL_SAI_CSR_xF_MASK; if (flags) - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), flags | xcsr); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), flags | xcsr); out: if (irq_none) @@ -137,21 +205,11 @@ static int fsl_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, return 0; } -static int fsl_sai_set_dai_bclk_ratio(struct snd_soc_dai *dai, - unsigned int ratio) -{ - struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); - - sai->bclk_ratio = ratio; - - return 0; -} - static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int fsl_dir) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; bool tx = fsl_dir == FSL_FMT_TRANSMITTER; u32 val_cr2 = 0; @@ -172,20 +230,85 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, return -EINVAL; } - regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs), + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, offset), FSL_SAI_CR2_MSEL_MASK, val_cr2); return 0; } +static int fsl_sai_set_mclk_rate(struct snd_soc_dai *dai, int clk_id, + unsigned int freq) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); + struct clk *p = sai->mclk_clk[clk_id], *pll = 0, *npll = 0; + u64 ratio = freq; + int ret; + + while (p && sai->pll8k_clk && sai->pll11k_clk) { + struct clk *pp = clk_get_parent(p); + + if (clk_is_match(pp, sai->pll8k_clk) || + clk_is_match(pp, sai->pll11k_clk)) { + pll = pp; + break; + } + p = pp; + } + + if (pll) { + npll = (do_div(ratio, 8000) ? sai->pll11k_clk : sai->pll8k_clk); + if (!clk_is_match(pll, npll)) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(dai->dev, + "failed to set parent %s: %d\n", + __clk_get_name(npll), ret); + } + } + + ret = clk_set_rate(sai->mclk_clk[clk_id], freq); + if (ret < 0) + dev_err(dai->dev, "failed to set clock rate (%u): %d\n", + freq, ret); + + return ret; +} + +static int fsl_sai_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); + + sai->bitclk_ratio = ratio; + return 0; +} + static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret; if (dir == SND_SOC_CLOCK_IN) return 0; + if (freq > 0 && clk_id != FSL_SAI_CLK_BUS) { + if (clk_id < 0 || clk_id >= FSL_SAI_MCLK_MAX) { + dev_err(cpu_dai->dev, "Unknown clock id: %d\n", clk_id); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(sai->mclk_clk[clk_id])) { + dev_err(cpu_dai->dev, "Unassigned clock: %d\n", clk_id); + return -EINVAL; + } + + if (sai->mclk_streams == 0) { + ret = fsl_sai_set_mclk_rate(cpu_dai, clk_id, freq); + if (ret < 0) + return ret; + } + } + ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, FSL_FMT_TRANSMITTER); if (ret) { @@ -205,13 +328,14 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, unsigned int fmt, int fsl_dir) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; bool tx = fsl_dir == FSL_FMT_TRANSMITTER; u32 val_cr2 = 0, val_cr4 = 0; if (!sai->is_lsb_first) val_cr4 |= FSL_SAI_CR4_MF; + sai->is_dsp_mode = false; /* DAI mode */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: @@ -250,6 +374,11 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, val_cr2 |= FSL_SAI_CR2_BCP; sai->is_dsp_mode = true; break; + case SND_SOC_DAIFMT_PDM: + val_cr2 |= FSL_SAI_CR2_BCP; + val_cr4 &= ~FSL_SAI_CR4_MF; + sai->is_dsp_mode = true; + break; case SND_SOC_DAIFMT_RIGHT_J: /* To be done */ default: @@ -278,31 +407,31 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, return -EINVAL; } + sai->slave_mode[tx] = false; + /* DAI clock master masks */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; val_cr4 |= FSL_SAI_CR4_FSD_MSTR; - sai->is_slave_mode = false; break; case SND_SOC_DAIFMT_CBM_CFM: - sai->is_slave_mode = true; + sai->slave_mode[tx] = true; break; case SND_SOC_DAIFMT_CBS_CFM: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; - sai->is_slave_mode = false; break; case SND_SOC_DAIFMT_CBM_CFS: val_cr4 |= FSL_SAI_CR4_FSD_MSTR; - sai->is_slave_mode = true; + sai->slave_mode[tx] = true; break; default: return -EINVAL; } - regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs), + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, offset), FSL_SAI_CR2_BCP | FSL_SAI_CR2_BCD_MSTR, val_cr2); - regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs), + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset), FSL_SAI_CR4_MF | FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP | FSL_SAI_CR4_FSD_MSTR, val_cr4); @@ -311,14 +440,23 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret; + if (sai->masterflag[FSL_FMT_TRANSMITTER]) + fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) | + sai->masterflag[FSL_FMT_TRANSMITTER]; + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER); if (ret) { dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret); return ret; } + if (sai->masterflag[FSL_FMT_RECEIVER]) + fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) | + sai->masterflag[FSL_FMT_RECEIVER]; + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER); if (ret) dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret); @@ -326,17 +464,68 @@ static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) return ret; } +static int fsl_sai_check_ver(struct device *dev) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned char offset = sai->soc->reg_offset; + unsigned int val; + int ret; + + if (FSL_SAI_TCSR(offset) == FSL_SAI_VERID) + return 0; + + if (sai->verid.loaded) + return 0; + + ret = regmap_read(sai->regmap, FSL_SAI_VERID, &val); + if (ret < 0) + return ret; + + dev_dbg(dev, "VERID: 0x%016X\n", val); + + sai->verid.id = (val & FSL_SAI_VER_ID_MASK) >> FSL_SAI_VER_ID_SHIFT; + sai->verid.extfifo_en = (val & FSL_SAI_VER_EFIFO_EN); + sai->verid.timestamp_en = (val & FSL_SAI_VER_TSTMP_EN); + + ret = regmap_read(sai->regmap, FSL_SAI_PARAM, &val); + if (ret < 0) + return ret; + + dev_dbg(dev, "PARAM: 0x%016X\n", val); + + /* max slots per frame, power of 2 */ + sai->param.spf = 1 << + ((val & FSL_SAI_PAR_SPF_MASK) >> FSL_SAI_PAR_SPF_SHIFT); + + /* words per fifo, power of 2 */ + sai->param.wpf = 1 << + ((val & FSL_SAI_PAR_WPF_MASK) >> FSL_SAI_PAR_WPF_SHIFT); + + /* number of datalines implemented */ + sai->param.dln = val & FSL_SAI_PAR_DLN_MASK; + + dev_dbg(dev, + "Version: 0x%08X, SPF: %u, WPF: %u, DLN: %u\n", + sai->verid.id, sai->param.spf, sai->param.wpf, sai->param.dln + ); + + sai->verid.loaded = true; + + return 0; +} + static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; unsigned long clk_rate; - u32 savediv = 0, ratio, savesub = freq; + unsigned int reg = 0; + u32 ratio, savesub = freq, saveratio = 0, savediv = 0; u32 id; int ret = 0; /* Don't apply to slave mode */ - if (sai->is_slave_mode) + if (sai->slave_mode[tx]) return 0; for (id = 0; id < FSL_SAI_MCLK_MAX; id++) { @@ -359,22 +548,21 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) "ratio %d for freq %dHz based on clock %ldHz\n", ratio, freq, clk_rate); - if (ratio % 2 == 0 && ratio >= 2 && ratio <= 512) - ratio /= 2; - else - continue; + if ((ratio % 2 == 0 && ratio >= 2 && ratio <= 512) || + (ratio == 1 && sai->verid.id >= FSL_SAI_VERID_0301)) { - if (ret < savesub) { - savediv = ratio; - sai->mclk_id[tx] = id; - savesub = ret; - } + if (ret < savesub) { + saveratio = ratio; + sai->mclk_id[tx] = id; + savesub = ret; + } - if (ret == 0) - break; + if (ret == 0) + break; + } } - if (savediv == 0) { + if (saveratio == 0) { dev_err(dai->dev, "failed to derive required %cx rate: %d\n", tx ? 'T' : 'R', freq); return -EINVAL; @@ -390,24 +578,32 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) * 4) For Tx and Rx are both Synchronous with another SAI, we just * ignore it. */ - if ((sai->synchronous[TX] && !sai->synchronous[RX]) || - (!tx && !sai->synchronous[RX])) { - regmap_update_bits(sai->regmap, FSL_SAI_RCR2(ofs), - FSL_SAI_CR2_MSEL_MASK, - FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); - regmap_update_bits(sai->regmap, FSL_SAI_RCR2(ofs), - FSL_SAI_CR2_DIV_MASK, savediv - 1); - } else if ((sai->synchronous[RX] && !sai->synchronous[TX]) || - (tx && !sai->synchronous[TX])) { - regmap_update_bits(sai->regmap, FSL_SAI_TCR2(ofs), - FSL_SAI_CR2_MSEL_MASK, - FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); - regmap_update_bits(sai->regmap, FSL_SAI_TCR2(ofs), - FSL_SAI_CR2_DIV_MASK, savediv - 1); + if ((!tx || sai->synchronous[TX]) && !sai->synchronous[RX]) + reg = FSL_SAI_RCR2(offset); + else if ((tx || sai->synchronous[RX]) && !sai->synchronous[TX]) + reg = FSL_SAI_TCR2(offset); + + if (reg) { + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_MSEL_MASK, + FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); + + savediv = (saveratio == 1 ? 0 : (saveratio >> 1) - 1); + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_DIV_MASK, savediv); + + if (sai->verid.id >= FSL_SAI_VERID_0301) { + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_BYP, + (saveratio == 1 ? FSL_SAI_CR2_BYP : 0)); + } } - dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n", - sai->mclk_id[tx], savediv, savesub); + if (sai->verid.id >= FSL_SAI_VERID_0301) { + /* SAI is in master mode at this point, so enable MCLK */ + regmap_update_bits(sai->regmap, FSL_SAI_MCTL, + FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN); + } + + dev_dbg(dai->dev, "best fit: clock id=%d, ratio=%d, deviation=%d\n", + sai->mclk_id[tx], saveratio, savesub); return 0; } @@ -417,30 +613,65 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; unsigned int channels = params_channels(params); u32 word_width = params_width(params); + u32 rate = params_rate(params); u32 val_cr4 = 0, val_cr5 = 0; u32 slots = (channels == 1) ? 2 : channels; u32 slot_width = word_width; - int ret; + u32 pins, bclk; + int ret, i, trce_mask = 0, dl_cfg_cnt, dl_cfg_idx = 0; + struct fsl_sai_dl_cfg *dl_cfg; if (sai->slots) slots = sai->slots; + pins = DIV_ROUND_UP(channels, slots); + sai->is_dsd = fsl_is_dsd(params); + if (sai->is_dsd) { + pins = channels; + dl_cfg = sai->dsd_dl_cfg; + dl_cfg_cnt = sai->dsd_dl_cfg_cnt; + } else { + dl_cfg = sai->pcm_dl_cfg; + dl_cfg_cnt = sai->pcm_dl_cfg_cnt; + } + + for (i = 0; i < dl_cfg_cnt; i++) { + if (dl_cfg[i].pins == pins) { + dl_cfg_idx = i; + break; + } + } + + if (dl_cfg_idx >= dl_cfg_cnt) { + dev_err(cpu_dai->dev, "fsl,dataline%s invalid or not provided.\n", + sai->is_dsd ? ",dsd" : ""); + return -EINVAL; + } + if (sai->slot_width) slot_width = sai->slot_width; - if (!sai->is_slave_mode) { - if (sai->bclk_ratio) - ret = fsl_sai_set_bclk(cpu_dai, tx, - sai->bclk_ratio * - params_rate(params)); - else - ret = fsl_sai_set_bclk(cpu_dai, tx, - slots * slot_width * - params_rate(params)); + bclk = rate*(sai->bitclk_ratio ? sai->bitclk_ratio : slots * slot_width); + + if (!IS_ERR_OR_NULL(sai->pinctrl)) { + sai->pins_state = fsl_get_pins_state(sai->pinctrl, params, bclk); + + if (!IS_ERR_OR_NULL(sai->pins_state)) { + ret = pinctrl_select_state(sai->pinctrl, sai->pins_state); + if (ret) { + dev_err(cpu_dai->dev, + "failed to set proper pins state: %d\n", ret); + return ret; + } + } + } + + if (!sai->slave_mode[tx]) { + ret = fsl_sai_set_bclk(cpu_dai, tx, bclk); if (ret) return ret; @@ -460,13 +691,18 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, val_cr5 |= FSL_SAI_CR5_WNW(slot_width); val_cr5 |= FSL_SAI_CR5_W0W(slot_width); - if (sai->is_lsb_first) + if (sai->is_lsb_first || sai->is_dsd) val_cr5 |= FSL_SAI_CR5_FBT(0); else val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1); val_cr4 |= FSL_SAI_CR4_FRSZ(slots); + /* Output Mode - data pins transmit 0 when slots are masked + * or channels are disabled + */ + val_cr4 |= FSL_SAI_CR4_CHMOD; + /* * For SAI master mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will * generate bclk and frame clock for Tx(Rx), we should set RCR4(TCR4), @@ -474,36 +710,78 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, * error. */ - if (!sai->is_slave_mode) { + if (!sai->slave_mode[tx]) { if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) { - regmap_update_bits(sai->regmap, FSL_SAI_TCR4(ofs), - FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + regmap_update_bits(sai->regmap, FSL_SAI_TCR4(offset), + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | + FSL_SAI_CR4_CHMOD_MASK, val_cr4); - regmap_update_bits(sai->regmap, FSL_SAI_TCR5(ofs), + regmap_update_bits(sai->regmap, FSL_SAI_TCR5(offset), FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | FSL_SAI_CR5_FBT_MASK, val_cr5); - regmap_write(sai->regmap, FSL_SAI_TMR, - ~0UL - ((1 << channels) - 1)); } else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) { - regmap_update_bits(sai->regmap, FSL_SAI_RCR4(ofs), - FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + regmap_update_bits(sai->regmap, FSL_SAI_RCR4(offset), + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | + FSL_SAI_CR4_CHMOD_MASK, val_cr4); - regmap_update_bits(sai->regmap, FSL_SAI_RCR5(ofs), + regmap_update_bits(sai->regmap, FSL_SAI_RCR5(offset), FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | FSL_SAI_CR5_FBT_MASK, val_cr5); - regmap_write(sai->regmap, FSL_SAI_RMR, - ~0UL - ((1 << channels) - 1)); } } - regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs), - FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + if (sai->soc->dataline != 0x1) { + + if (dl_cfg[dl_cfg_idx].mask[tx] <= 1 || sai->is_multi_lane) + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset), + FSL_SAI_CR4_FCOMB_MASK, 0); + else + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset), + FSL_SAI_CR4_FCOMB_MASK, FSL_SAI_CR4_FCOMB_SOFT); + + if (sai->is_multi_lane) { + if (tx) { + sai->dma_params_tx.maxburst = + FSL_SAI_MAXBURST_TX * pins; + sai->dma_params_tx.fifo_num = pins + + (dl_cfg[dl_cfg_idx].offset[tx] << 4); + } else { + sai->dma_params_rx.maxburst = + FSL_SAI_MAXBURST_RX * pins; + sai->dma_params_rx.fifo_num = pins + + (dl_cfg[dl_cfg_idx].offset[tx] << 4); + } + } + + snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx, + &sai->dma_params_rx); + } + + if (__sw_hweight8(dl_cfg[dl_cfg_idx].mask[tx] & 0xFF) < pins) { + dev_err(cpu_dai->dev, "channel not supported\n"); + return -EINVAL; + } + + /*find a proper tcre setting*/ + for (i = 0; i < 8; i++) { + trce_mask = (1 << (i + 1)) - 1; + if (__sw_hweight8(dl_cfg[dl_cfg_idx].mask[tx] & trce_mask) == pins) + break; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset), + FSL_SAI_CR3_TRCE_MASK, + FSL_SAI_CR3_TRCE((dl_cfg[dl_cfg_idx].mask[tx] & trce_mask))); + + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset), + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | + FSL_SAI_CR4_CHMOD_MASK, val_cr4); - regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx, ofs), + regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx, offset), FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | FSL_SAI_CR5_FBT_MASK, val_cr5); - regmap_write(sai->regmap, FSL_SAI_xMR(tx), ~0UL - ((1 << channels) - 1)); - + regmap_write(sai->regmap, FSL_SAI_xMR(tx), + ~0UL - ((1 << min(channels, slots)) - 1)); return 0; } @@ -511,9 +789,13 @@ static int fsl_sai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + unsigned char offset = sai->soc->reg_offset; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - if (!sai->is_slave_mode && + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset), + FSL_SAI_CR3_TRCE_MASK, 0); + + if (!sai->slave_mode[tx] && sai->mclk_streams & BIT(substream->stream)) { clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[tx]]); sai->mclk_streams &= ~BIT(substream->stream); @@ -527,19 +809,46 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); - unsigned int ofs = sai->soc_data->reg_offset; - + unsigned char offset = sai->soc->reg_offset; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 channels = substream->runtime->channels; + u32 slots = (channels == 1) ? 2 : channels; u32 xcsr, count = 100; + u32 pins; + int i = 0, j = 0, k = 0, dl_cfg_cnt, dl_cfg_idx = 0; + struct fsl_sai_dl_cfg *dl_cfg; + + if (sai->slots) + slots = sai->slots; + + pins = DIV_ROUND_UP(channels, slots); + + if (sai->is_dsd) { + pins = channels; + dl_cfg = sai->dsd_dl_cfg; + dl_cfg_cnt = sai->dsd_dl_cfg_cnt; + } else { + dl_cfg = sai->pcm_dl_cfg; + dl_cfg_cnt = sai->pcm_dl_cfg_cnt; + } + + for (i = 0; i < dl_cfg_cnt; i++) { + if (dl_cfg[i].pins == pins) { + dl_cfg_idx = i; + break; + } + } + + i = 0; /* * Asynchronous mode: Clear SYNC for both Tx and Rx. * Rx sync with Tx clocks: Clear SYNC for Tx, set it for Rx. * Tx sync with Rx clocks: Clear SYNC for Rx, set it for Tx. */ - regmap_update_bits(sai->regmap, FSL_SAI_TCR2(ofs), FSL_SAI_CR2_SYNC, - sai->synchronous[TX] ? FSL_SAI_CR2_SYNC : 0); - regmap_update_bits(sai->regmap, FSL_SAI_RCR2(ofs), FSL_SAI_CR2_SYNC, + regmap_update_bits(sai->regmap, FSL_SAI_TCR2(offset), FSL_SAI_CR2_SYNC, + sai->synchronous[TX] ? FSL_SAI_CR2_SYNC : 0); + regmap_update_bits(sai->regmap, FSL_SAI_RCR2(offset), FSL_SAI_CR2_SYNC, sai->synchronous[RX] ? FSL_SAI_CR2_SYNC : 0); /* @@ -550,44 +859,63 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), + + while (tx && i < channels) { + if (dl_cfg[dl_cfg_idx].mask[tx] & (1 << j)) { + regmap_write(sai->regmap, FSL_SAI_TDR0 + j * 0x4, 0x0); + i++; + k++; + } + j++; + + if (k%pins == 0) + j = 0; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE); - regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs), + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), + FSL_SAI_CSR_SE, FSL_SAI_CSR_SE); + if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) { + regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), offset), FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); - regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs), + } else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) { + regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), offset), FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + } - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_FRDE, 0); - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset), FSL_SAI_CSR_xIE_MASK, 0); /* Check if the opposite FRDE is also disabled */ - regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, ofs), &xcsr); + regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, offset), &xcsr); if (!(xcsr & FSL_SAI_CSR_FRDE)) { /* Disable both directions and reset their FIFOs */ - regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs), + regmap_update_bits(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_TERE, 0); - regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs), + regmap_update_bits(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_TERE, 0); /* TERE will remain set till the end of current frame */ do { udelay(10); - regmap_read(sai->regmap, - FSL_SAI_xCSR(tx, ofs), &xcsr); + regmap_read(sai->regmap, FSL_SAI_xCSR(tx, offset), &xcsr); } while (--count && xcsr & FSL_SAI_CSR_TERE); - regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs), + regmap_update_bits(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); - regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs), + regmap_update_bits(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); /* @@ -597,15 +925,15 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, * This is a hardware bug, and will be fix in the * next sai version. */ - if (!sai->is_slave_mode) { + if (!sai->slave_mode[tx]) { /* Software Reset for both Tx and Rx */ - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), - FSL_SAI_CSR_SR); - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), - FSL_SAI_CSR_SR); + regmap_write(sai->regmap, + FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR); + regmap_write(sai->regmap, + FSL_SAI_RCSR(offset), FSL_SAI_CSR_SR); /* Clear SR bit to finish the reset */ - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), 0); - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), 0); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0); } } break; @@ -620,19 +948,16 @@ static int fsl_sai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); - unsigned int ofs = sai->soc_data->reg_offset; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; int ret; - regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs), - FSL_SAI_CR3_TRCE_MASK, - FSL_SAI_CR3_TRCE); + if (sai->is_stream_opened[tx]) + return -EBUSY; + else + sai->is_stream_opened[tx] = true; - /* - * EDMA controller needs period size to be a multiple of - * tx/rx maxburst - */ - if (sai->soc_data->use_edma) + /* EDMA engine needs periods of size multiple of tx/rx maxburst */ + if (sai->soc->constrain_period_size) snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, tx ? sai->dma_params_tx.maxburst : @@ -648,15 +973,14 @@ static void fsl_sai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); - unsigned int ofs = sai->soc_data->reg_offset; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs), - FSL_SAI_CR3_TRCE_MASK, 0); + if (sai->is_stream_opened[tx]) + sai->is_stream_opened[tx] = false; } static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { - .set_bclk_ratio = fsl_sai_set_dai_bclk_ratio, + .set_bclk_ratio = fsl_sai_set_dai_bclk_ratio, .set_sysclk = fsl_sai_set_dai_sysclk, .set_fmt = fsl_sai_set_dai_fmt, .set_tdm_slot = fsl_sai_set_dai_tdm_slot, @@ -670,21 +994,14 @@ static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); - unsigned int ofs = sai->soc_data->reg_offset; - - /* Software Reset for both Tx and Rx */ - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), FSL_SAI_CSR_SR); - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), FSL_SAI_CSR_SR); - /* Clear SR bit to finish the reset */ - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), 0); - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), 0); - - regmap_update_bits(sai->regmap, FSL_SAI_TCR1(ofs), - FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth), - sai->soc_data->fifo_depth - FSL_SAI_MAXBURST_TX); - regmap_update_bits(sai->regmap, FSL_SAI_RCR1(ofs), - FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth), - FSL_SAI_MAXBURST_RX - 1); + unsigned char offset = sai->soc->reg_offset; + + regmap_update_bits(sai->regmap, FSL_SAI_TCR1(offset), + FSL_SAI_CR1_RFW_MASK(sai->soc->fifo_depth), + sai->soc->fifo_depth - FSL_SAI_MAXBURST_TX); + regmap_update_bits(sai->regmap, FSL_SAI_RCR1(offset), + FSL_SAI_CR1_RFW_MASK(sai->soc->fifo_depth), + FSL_SAI_MAXBURST_RX - 1); snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx, &sai->dma_params_rx); @@ -694,6 +1011,23 @@ static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) return 0; } +static int fsl_sai_dai_resume(struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + if (!IS_ERR_OR_NULL(sai->pinctrl) && !IS_ERR_OR_NULL(sai->pins_state)) { + ret = pinctrl_select_state(sai->pinctrl, sai->pins_state); + if (ret) { + dev_err(cpu_dai->dev, + "failed to set proper pins state: %d\n", ret); + return ret; + } + } + + return 0; +} + static struct snd_soc_dai_driver fsl_sai_dai_template = { .probe = fsl_sai_dai_probe, .playback = { @@ -701,7 +1035,7 @@ static struct snd_soc_dai_driver fsl_sai_dai_template = { .channels_min = 1, .channels_max = 32, .rate_min = 8000, - .rate_max = 192000, + .rate_max = 2822400, .rates = SNDRV_PCM_RATE_KNOT, .formats = FSL_SAI_FORMATS, }, @@ -710,18 +1044,19 @@ static struct snd_soc_dai_driver fsl_sai_dai_template = { .channels_min = 1, .channels_max = 32, .rate_min = 8000, - .rate_max = 192000, + .rate_max = 2822400, .rates = SNDRV_PCM_RATE_KNOT, .formats = FSL_SAI_FORMATS, }, + .resume = fsl_sai_dai_resume, .ops = &fsl_sai_pcm_dai_ops, }; static const struct snd_soc_component_driver fsl_component = { - .name = "fsl-sai", + .name = "fsl-sai", }; -static struct reg_default fsl_sai_reg_defaults_ofs0[] = { +static struct reg_default fsl_sai_v2_reg_defaults[] = { {FSL_SAI_TCR1(0), 0}, {FSL_SAI_TCR2(0), 0}, {FSL_SAI_TCR3(0), 0}, @@ -729,22 +1064,16 @@ static struct reg_default fsl_sai_reg_defaults_ofs0[] = { {FSL_SAI_TCR5(0), 0}, {FSL_SAI_TDR0, 0}, {FSL_SAI_TDR1, 0}, - {FSL_SAI_TDR2, 0}, - {FSL_SAI_TDR3, 0}, - {FSL_SAI_TDR4, 0}, - {FSL_SAI_TDR5, 0}, - {FSL_SAI_TDR6, 0}, - {FSL_SAI_TDR7, 0}, - {FSL_SAI_TMR, 0}, + {FSL_SAI_TMR, 0}, {FSL_SAI_RCR1(0), 0}, {FSL_SAI_RCR2(0), 0}, {FSL_SAI_RCR3(0), 0}, {FSL_SAI_RCR4(0), 0}, {FSL_SAI_RCR5(0), 0}, - {FSL_SAI_RMR, 0}, + {FSL_SAI_RMR, 0}, }; -static struct reg_default fsl_sai_reg_defaults_ofs8[] = { +static struct reg_default fsl_sai_v3_reg_defaults[] = { {FSL_SAI_TCR1(8), 0}, {FSL_SAI_TCR2(8), 0}, {FSL_SAI_TCR3(8), 0}, @@ -758,24 +1087,26 @@ static struct reg_default fsl_sai_reg_defaults_ofs8[] = { {FSL_SAI_TDR5, 0}, {FSL_SAI_TDR6, 0}, {FSL_SAI_TDR7, 0}, - {FSL_SAI_TMR, 0}, + {FSL_SAI_TMR, 0}, {FSL_SAI_RCR1(8), 0}, {FSL_SAI_RCR2(8), 0}, {FSL_SAI_RCR3(8), 0}, {FSL_SAI_RCR4(8), 0}, {FSL_SAI_RCR5(8), 0}, - {FSL_SAI_RMR, 0}, + {FSL_SAI_RMR, 0}, + {FSL_SAI_MCTL, 0}, + {FSL_SAI_MDIV, 0}, }; static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) { struct fsl_sai *sai = dev_get_drvdata(dev); - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; - if (reg >= FSL_SAI_TCSR(ofs) && reg <= FSL_SAI_TCR5(ofs)) + if (reg >= FSL_SAI_TCSR(offset) && reg <= FSL_SAI_TCR5(offset)) return true; - if (reg >= FSL_SAI_RCSR(ofs) && reg <= FSL_SAI_RCR5(ofs)) + if (reg >= FSL_SAI_RCSR(offset) && reg <= FSL_SAI_RCR5(offset)) return true; switch (reg) { @@ -805,6 +1136,18 @@ static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) case FSL_SAI_RFR6: case FSL_SAI_RFR7: case FSL_SAI_RMR: + case FSL_SAI_MCTL: + case FSL_SAI_MDIV: + case FSL_SAI_VERID: + case FSL_SAI_PARAM: + case FSL_SAI_TTCTN: + case FSL_SAI_RTCTN: + case FSL_SAI_TTCTL: + case FSL_SAI_TBCTN: + case FSL_SAI_TTCAP: + case FSL_SAI_RTCTL: + case FSL_SAI_RBCTN: + case FSL_SAI_RTCAP: return true; default: return false; @@ -814,9 +1157,13 @@ static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) { struct fsl_sai *sai = dev_get_drvdata(dev); - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; + + if (reg == FSL_SAI_TCSR(offset) || reg == FSL_SAI_RCSR(offset)) + return true; - if (reg == FSL_SAI_TCSR(ofs) || reg == FSL_SAI_RCSR(ofs)) + if (sai->soc->reg_offset == 8 && (reg == FSL_SAI_VERID || + reg == FSL_SAI_PARAM)) return true; switch (reg) { @@ -844,6 +1191,14 @@ static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) case FSL_SAI_RDR5: case FSL_SAI_RDR6: case FSL_SAI_RDR7: + case FSL_SAI_TTCTN: + case FSL_SAI_TTCTL: + case FSL_SAI_TBCTN: + case FSL_SAI_TTCAP: + case FSL_SAI_RTCTN: + case FSL_SAI_RTCTL: + case FSL_SAI_RBCTN: + case FSL_SAI_RTCAP: return true; default: return false; @@ -853,12 +1208,12 @@ static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) { struct fsl_sai *sai = dev_get_drvdata(dev); - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; - if (reg >= FSL_SAI_TCSR(ofs) && reg <= FSL_SAI_TCR5(ofs)) + if (reg >= FSL_SAI_TCSR(offset) && reg <= FSL_SAI_TCR5(offset)) return true; - if (reg >= FSL_SAI_RCSR(ofs) && reg <= FSL_SAI_RCR5(ofs)) + if (reg >= FSL_SAI_RCSR(offset) && reg <= FSL_SAI_RCR5(offset)) return true; switch (reg) { @@ -872,30 +1227,130 @@ static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) case FSL_SAI_TDR7: case FSL_SAI_TMR: case FSL_SAI_RMR: + case FSL_SAI_MCTL: + case FSL_SAI_MDIV: + case FSL_SAI_TTCTL: + case FSL_SAI_RTCTL: return true; default: return false; } } -static struct regmap_config fsl_sai_regmap_config = { +static const struct regmap_config fsl_sai_v2_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .fast_io = true, .max_register = FSL_SAI_RMR, - .reg_defaults = fsl_sai_reg_defaults_ofs0, - .num_reg_defaults = ARRAY_SIZE(fsl_sai_reg_defaults_ofs0), + .reg_defaults = fsl_sai_v2_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_sai_v2_reg_defaults), + .readable_reg = fsl_sai_readable_reg, + .volatile_reg = fsl_sai_volatile_reg, + .writeable_reg = fsl_sai_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config fsl_sai_v3_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FSL_SAI_MDIV, + .reg_defaults = fsl_sai_v3_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_sai_v3_reg_defaults), .readable_reg = fsl_sai_readable_reg, .volatile_reg = fsl_sai_volatile_reg, .writeable_reg = fsl_sai_writeable_reg, .cache_type = REGCACHE_FLAT, }; +static const struct of_device_id fsl_sai_ids[] = { + { .compatible = "fsl,vf610-sai", .data = &fsl_sai_vf610 }, + { .compatible = "fsl,imx6sx-sai", .data = &fsl_sai_imx6sx }, + { .compatible = "fsl,imx6ul-sai", .data = &fsl_sai_imx6ul }, + { .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp }, + { .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq }, + { .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_sai_ids); + +static unsigned int fsl_sai_calc_dl_off(unsigned long dl_mask) +{ + int fbidx, nbidx, offset; + + fbidx = find_first_bit(&dl_mask, 8); + nbidx = find_next_bit(&dl_mask, 8, fbidx + 1); + offset = nbidx - fbidx - 1; + + return (offset < 0 || offset >= 7 ? 0 : offset); +} + +static int fsl_sai_read_dlcfg(struct platform_device *pdev, char *pn, + struct fsl_sai_dl_cfg **rcfg, unsigned int soc_dl) +{ + int ret, elems, i, index, num_cfg; + struct device_node *np = pdev->dev.of_node; + struct fsl_sai_dl_cfg *cfg; + u32 rx, tx, pins; + + *rcfg = NULL; + + elems = of_property_count_u32_elems(np, pn); + + /* consider default value "0 0x1 0x1" if property is missing */ + if (elems <= 0) + elems = 3; + + if (elems % 3) { + dev_err(&pdev->dev, + "Number of elements in %s must be divisible to 3.\n", pn); + return -EINVAL; + } + + num_cfg = elems / 3; + cfg = devm_kzalloc(&pdev->dev, num_cfg * sizeof(*cfg), GFP_KERNEL); + if (cfg == NULL) { + dev_err(&pdev->dev, "Cannot allocate memory for %s.\n", pn); + return -ENOMEM; + } + + for (i = 0, index = 0; i < num_cfg; i++) { + ret = of_property_read_u32_index(np, pn, index++, &pins); + if (ret) + pins = 0; + + ret = of_property_read_u32_index(np, pn, index++, &rx); + if (ret) + rx = 1; + + ret = of_property_read_u32_index(np, pn, index++, &tx); + if (ret) + tx = 1; + + if ((rx & ~soc_dl) || (tx & ~soc_dl)) { + dev_err(&pdev->dev, + "%s: dataline cfg[%d] setting error, mask is 0x%x\n", + pn, i, soc_dl); + return -EINVAL; + } + + cfg[i].pins = pins; + cfg[i].mask[0] = rx; + cfg[i].offset[0] = fsl_sai_calc_dl_off(rx); + cfg[i].mask[1] = tx; + cfg[i].offset[1] = fsl_sai_calc_dl_off(tx); + } + + *rcfg = cfg; + return num_cfg; +} + static int fsl_sai_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; struct fsl_sai *sai; struct regmap *gpr; struct resource *res; @@ -903,34 +1358,35 @@ static int fsl_sai_probe(struct platform_device *pdev) char tmp[8]; int irq, ret, i; int index; + struct regmap_config fsl_sai_regmap_config = fsl_sai_v2_regmap_config; + unsigned long irqflags = 0; sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); if (!sai) return -ENOMEM; sai->pdev = pdev; - sai->soc_data = of_device_get_match_data(&pdev->dev); + + of_id = of_match_device(fsl_sai_ids, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; sai->is_lsb_first = of_property_read_bool(np, "lsb-first"); + sai->soc = of_id->data; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) return PTR_ERR(base); - if (sai->soc_data->reg_offset == 8) { - fsl_sai_regmap_config.reg_defaults = fsl_sai_reg_defaults_ofs8; - fsl_sai_regmap_config.num_reg_defaults = - ARRAY_SIZE(fsl_sai_reg_defaults_ofs8); - } + if (sai->soc->reg_offset == 8) + fsl_sai_regmap_config = fsl_sai_v3_regmap_config; - sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "bus", base, &fsl_sai_regmap_config); + memcpy(&sai->cpu_dai_drv, &fsl_sai_dai_template, + sizeof(fsl_sai_dai_template)); - /* Compatible with old DTB cases */ - if (IS_ERR(sai->regmap)) - sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "sai", base, &fsl_sai_regmap_config); + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + NULL, base, &fsl_sai_regmap_config); if (IS_ERR(sai->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); return PTR_ERR(sai->regmap); @@ -944,22 +1400,68 @@ static int fsl_sai_probe(struct platform_device *pdev) sai->bus_clk = NULL; } - sai->mclk_clk[0] = sai->bus_clk; - for (i = 1; i < FSL_SAI_MCLK_MAX; i++) { + for (i = 0; i < FSL_SAI_MCLK_MAX; i++) { sprintf(tmp, "mclk%d", i); sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp); if (IS_ERR(sai->mclk_clk[i])) { dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n", - i + 1, PTR_ERR(sai->mclk_clk[i])); + i, PTR_ERR(sai->mclk_clk[i])); sai->mclk_clk[i] = NULL; } } + sai->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(sai->pll8k_clk)) + sai->pll8k_clk = NULL; + + sai->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(sai->pll11k_clk)) + sai->pll11k_clk = NULL; + + if (of_find_property(np, "fsl,sai-multi-lane", NULL)) + sai->is_multi_lane = true; + + /*dataline mask for rx and tx*/ + ret = fsl_sai_read_dlcfg(pdev, "fsl,dataline", &sai->pcm_dl_cfg, + sai->soc->dataline); + if (ret < 0) + return ret; + + sai->pcm_dl_cfg_cnt = ret; + + ret = fsl_sai_read_dlcfg(pdev, "fsl,dataline,dsd", &sai->dsd_dl_cfg, + sai->soc->dataline); + if (ret < 0) + return ret; + + sai->dsd_dl_cfg_cnt = ret; + + if ((of_find_property(np, "fsl,i2s-xtor", NULL) != NULL) || + (of_find_property(np, "fsl,txm-rxs", NULL) != NULL)) + { + sai->masterflag[FSL_FMT_TRANSMITTER] = SND_SOC_DAIFMT_CBS_CFS; + sai->masterflag[FSL_FMT_RECEIVER] = SND_SOC_DAIFMT_CBM_CFM; + } else { + if (!of_property_read_u32(np, "fsl,txmasterflag", + &sai->masterflag[FSL_FMT_TRANSMITTER])) + sai->masterflag[FSL_FMT_TRANSMITTER] <<= 12; + if (!of_property_read_u32(np, "fsl,rxmasterflag", + &sai->masterflag[FSL_FMT_RECEIVER])) + sai->masterflag[FSL_FMT_RECEIVER] <<= 12; + } + irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); return irq; + } - ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, 0, np->name, sai); + /* SAI shared interrupt */ + if (of_property_read_bool(np, "fsl,shared-interrupt")) + irqflags = IRQF_SHARED; + + ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, irqflags, np->name, + sai); if (ret) { dev_err(&pdev->dev, "failed to claim irq %u\n", irq); return ret; @@ -995,6 +1497,14 @@ static int fsl_sai_probe(struct platform_device *pdev) sai->cpu_dai_drv.symmetric_samplebits = 0; } + platform_set_drvdata(pdev, sai); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + ret = fsl_sai_check_ver(&pdev->dev); + if (ret < 0) + dev_warn(&pdev->dev, "Error reading SAI version: %d\n", ret); + if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL) && of_device_is_compatible(np, "fsl,imx6ul-sai")) { gpr = syscon_regmap_lookup_by_compatible("fsl,imx6ul-iomuxc-gpr"); @@ -1011,96 +1521,76 @@ static int fsl_sai_probe(struct platform_device *pdev) MCLK_DIR(index)); } + if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL) && + sai->verid.id >= FSL_SAI_VERID_0301) { + regmap_update_bits(sai->regmap, FSL_SAI_MCTL, + FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN); + } + + if (sai->verid.timestamp_en) { + if (of_find_property(np, "fsl,sai-monitor-spdif", NULL) && + of_device_is_compatible(np, "fsl,imx8mm-sai")) { + sai->regmap_gpr = syscon_regmap_lookup_by_compatible("fsl,imx8mm-iomuxc-gpr"); + if (IS_ERR(sai->regmap_gpr)) + dev_warn(&pdev->dev, "cannot find iomuxc registers\n"); + + sai->gpr_idx = of_alias_get_id(np, "sai"); + if (sai->gpr_idx < 0) + dev_warn(&pdev->dev, "cannot find sai alias id\n"); + + if (sai->gpr_idx > 0 && !IS_ERR(sai->regmap_gpr)) + sai->monitor_spdif = true; + } + + if (sysfs_create_group(&pdev->dev.kobj, fsl_sai_get_dev_attribute_group(sai->monitor_spdif))) + dev_err(&pdev->dev, "fail to create sys group\n"); + } + + pm_runtime_put_sync(&pdev->dev); + + sai->dma_params_rx.chan_name = "rx"; + sai->dma_params_tx.chan_name = "tx"; sai->dma_params_rx.addr = res->start + FSL_SAI_RDR0; sai->dma_params_tx.addr = res->start + FSL_SAI_TDR0; sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX; sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX; - platform_set_drvdata(pdev, sai); + sai->pinctrl = devm_pinctrl_get(&pdev->dev); - pm_runtime_enable(&pdev->dev); + regcache_cache_only(sai->regmap, true); ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, &sai->cpu_dai_drv, 1); if (ret) - goto err_pm_disable; - - if (sai->soc_data->use_imx_pcm) { - ret = imx_pcm_dma_init(pdev, IMX_SAI_DMABUF_SIZE); - if (ret) - goto err_pm_disable; - } else { - ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) - goto err_pm_disable; - } - - return ret; - -err_pm_disable: - pm_runtime_disable(&pdev->dev); + return ret; - return ret; + if (sai->soc->imx) + return imx_pcm_platform_register(&pdev->dev); + else + return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); } static int fsl_sai_remove(struct platform_device *pdev) { + struct fsl_sai *sai = dev_get_drvdata(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (sai->verid.timestamp_en) + sysfs_remove_group(&pdev->dev.kobj, fsl_sai_get_dev_attribute_group(sai->monitor_spdif)); + return 0; } -static const struct fsl_sai_soc_data fsl_sai_vf610_data = { - .use_imx_pcm = false, - .use_edma = false, - .fifo_depth = 32, - .reg_offset = 0, -}; - -static const struct fsl_sai_soc_data fsl_sai_imx6sx_data = { - .use_imx_pcm = true, - .use_edma = false, - .fifo_depth = 32, - .reg_offset = 0, -}; - -static const struct fsl_sai_soc_data fsl_sai_imx7ulp_data = { - .use_imx_pcm = true, - .use_edma = false, - .fifo_depth = 16, - .reg_offset = 8, -}; - -static const struct fsl_sai_soc_data fsl_sai_imx8mq_data = { - .use_imx_pcm = true, - .use_edma = false, - .fifo_depth = 128, - .reg_offset = 8, -}; - -static const struct fsl_sai_soc_data fsl_sai_imx8qm_data = { - .use_imx_pcm = true, - .use_edma = true, - .fifo_depth = 64, - .reg_offset = 0, -}; - -static const struct of_device_id fsl_sai_ids[] = { - { .compatible = "fsl,vf610-sai", .data = &fsl_sai_vf610_data }, - { .compatible = "fsl,imx6sx-sai", .data = &fsl_sai_imx6sx_data }, - { .compatible = "fsl,imx6ul-sai", .data = &fsl_sai_imx6sx_data }, - { .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp_data }, - { .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq_data }, - { .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm_data }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, fsl_sai_ids); - #ifdef CONFIG_PM static int fsl_sai_runtime_suspend(struct device *dev) { struct fsl_sai *sai = dev_get_drvdata(dev); + regcache_cache_only(sai->regmap, true); + + release_bus_freq(BUS_FREQ_AUDIO); + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]); @@ -1109,6 +1599,9 @@ static int fsl_sai_runtime_suspend(struct device *dev) clk_disable_unprepare(sai->bus_clk); + if (sai->soc->flags & SAI_FLAG_PMQOS) + pm_qos_remove_request(&sai->pm_qos_req); + regcache_cache_only(sai->regmap, true); regcache_mark_dirty(sai->regmap); @@ -1118,7 +1611,7 @@ static int fsl_sai_runtime_suspend(struct device *dev) static int fsl_sai_runtime_resume(struct device *dev) { struct fsl_sai *sai = dev_get_drvdata(dev); - unsigned int ofs = sai->soc_data->reg_offset; + unsigned char offset = sai->soc->reg_offset; int ret; ret = clk_prepare_enable(sai->bus_clk); @@ -1139,12 +1632,20 @@ static int fsl_sai_runtime_resume(struct device *dev) goto disable_tx_clk; } + request_bus_freq(BUS_FREQ_AUDIO); + + if (sai->soc->flags & SAI_FLAG_PMQOS) + pm_qos_add_request(&sai->pm_qos_req, + PM_QOS_CPU_DMA_LATENCY, 0); + regcache_cache_only(sai->regmap, false); - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), FSL_SAI_CSR_SR); - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), FSL_SAI_CSR_SR); + regcache_mark_dirty(sai->regmap); + + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_SR); usleep_range(1000, 2000); - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), 0); - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), 0); + regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0); + regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0); ret = regcache_sync(sai->regmap); if (ret) diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index 677ecfc1ec68..8476cfffaa25 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -1,78 +1,94 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright 2012-2013 Freescale Semiconductor, Inc. + * Copyright 2012-2016 Freescale Semiconductor, Inc. */ #ifndef __FSL_SAI_H #define __FSL_SAI_H +#include <linux/pm_qos.h> #include <sound/dmaengine_pcm.h> #define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ - SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE |\ - SNDRV_PCM_FMTBIT_S32_LE) + SNDRV_PCM_FMTBIT_S32_LE |\ + SNDRV_PCM_FMTBIT_DSD_U8 |\ + SNDRV_PCM_FMTBIT_DSD_U16_LE |\ + SNDRV_PCM_FMTBIT_DSD_U32_LE) /* SAI Register Map Register */ -#define FSL_SAI_TCSR(ofs) (0x00 + ofs) /* SAI Transmit Control */ -#define FSL_SAI_TCR1(ofs) (0x04 + ofs) /* SAI Transmit Configuration 1 */ -#define FSL_SAI_TCR2(ofs) (0x08 + ofs) /* SAI Transmit Configuration 2 */ -#define FSL_SAI_TCR3(ofs) (0x0c + ofs) /* SAI Transmit Configuration 3 */ -#define FSL_SAI_TCR4(ofs) (0x10 + ofs) /* SAI Transmit Configuration 4 */ -#define FSL_SAI_TCR5(ofs) (0x14 + ofs) /* SAI Transmit Configuration 5 */ -#define FSL_SAI_TDR0 0x20 /* SAI Transmit Data 0 */ -#define FSL_SAI_TDR1 0x24 /* SAI Transmit Data 1 */ -#define FSL_SAI_TDR2 0x28 /* SAI Transmit Data 2 */ -#define FSL_SAI_TDR3 0x2C /* SAI Transmit Data 3 */ -#define FSL_SAI_TDR4 0x30 /* SAI Transmit Data 4 */ -#define FSL_SAI_TDR5 0x34 /* SAI Transmit Data 5 */ -#define FSL_SAI_TDR6 0x38 /* SAI Transmit Data 6 */ -#define FSL_SAI_TDR7 0x3C /* SAI Transmit Data 7 */ -#define FSL_SAI_TFR0 0x40 /* SAI Transmit FIFO 0 */ -#define FSL_SAI_TFR1 0x44 /* SAI Transmit FIFO 1 */ -#define FSL_SAI_TFR2 0x48 /* SAI Transmit FIFO 2 */ -#define FSL_SAI_TFR3 0x4C /* SAI Transmit FIFO 3 */ -#define FSL_SAI_TFR4 0x50 /* SAI Transmit FIFO 4 */ -#define FSL_SAI_TFR5 0x54 /* SAI Transmit FIFO 5 */ -#define FSL_SAI_TFR6 0x58 /* SAI Transmit FIFO 6 */ -#define FSL_SAI_TFR7 0x5C /* SAI Transmit FIFO 7 */ +#define FSL_SAI_VERID 0x00 /* SAI Version ID Register */ +#define FSL_SAI_PARAM 0x04 /* SAI Parameter Register */ +#define FSL_SAI_TCSR(offset) (0x00 + offset) /* SAI Transmit Control */ +#define FSL_SAI_TCR1(offset) (0x04 + offset) /* SAI Transmit Configuration 1 */ +#define FSL_SAI_TCR2(offset) (0x08 + offset) /* SAI Transmit Configuration 2 */ +#define FSL_SAI_TCR3(offset) (0x0c + offset) /* SAI Transmit Configuration 3 */ +#define FSL_SAI_TCR4(offset) (0x10 + offset) /* SAI Transmit Configuration 4 */ +#define FSL_SAI_TCR5(offset) (0x14 + offset) /* SAI Transmit Configuration 5 */ +#define FSL_SAI_TDR0 0x20 /* SAI Transmit Data */ +#define FSL_SAI_TDR1 0x24 /* SAI Transmit Data */ +#define FSL_SAI_TDR2 0x28 /* SAI Transmit Data */ +#define FSL_SAI_TDR3 0x2C /* SAI Transmit Data */ +#define FSL_SAI_TDR4 0x30 /* SAI Transmit Data */ +#define FSL_SAI_TDR5 0x34 /* SAI Transmit Data */ +#define FSL_SAI_TDR6 0x38 /* SAI Transmit Data */ +#define FSL_SAI_TDR7 0x3C /* SAI Transmit Data */ +#define FSL_SAI_TFR0 0x40 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR1 0x44 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR2 0x48 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR3 0x4C /* SAI Transmit FIFO */ +#define FSL_SAI_TFR4 0x50 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR5 0x54 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR6 0x58 /* SAI Transmit FIFO */ +#define FSL_SAI_TFR7 0x5C /* SAI Transmit FIFO */ #define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */ -#define FSL_SAI_RCSR(ofs) (0x80 + ofs) /* SAI Receive Control */ -#define FSL_SAI_RCR1(ofs) (0x84 + ofs)/* SAI Receive Configuration 1 */ -#define FSL_SAI_RCR2(ofs) (0x88 + ofs) /* SAI Receive Configuration 2 */ -#define FSL_SAI_RCR3(ofs) (0x8c + ofs) /* SAI Receive Configuration 3 */ -#define FSL_SAI_RCR4(ofs) (0x90 + ofs) /* SAI Receive Configuration 4 */ -#define FSL_SAI_RCR5(ofs) (0x94 + ofs) /* SAI Receive Configuration 5 */ -#define FSL_SAI_RDR0 0xa0 /* SAI Receive Data 0 */ -#define FSL_SAI_RDR1 0xa4 /* SAI Receive Data 1 */ -#define FSL_SAI_RDR2 0xa8 /* SAI Receive Data 2 */ -#define FSL_SAI_RDR3 0xac /* SAI Receive Data 3 */ -#define FSL_SAI_RDR4 0xb0 /* SAI Receive Data 4 */ -#define FSL_SAI_RDR5 0xb4 /* SAI Receive Data 5 */ -#define FSL_SAI_RDR6 0xb8 /* SAI Receive Data 6 */ -#define FSL_SAI_RDR7 0xbc /* SAI Receive Data 7 */ -#define FSL_SAI_RFR0 0xc0 /* SAI Receive FIFO 0 */ -#define FSL_SAI_RFR1 0xc4 /* SAI Receive FIFO 1 */ -#define FSL_SAI_RFR2 0xc8 /* SAI Receive FIFO 2 */ -#define FSL_SAI_RFR3 0xcc /* SAI Receive FIFO 3 */ -#define FSL_SAI_RFR4 0xd0 /* SAI Receive FIFO 4 */ -#define FSL_SAI_RFR5 0xd4 /* SAI Receive FIFO 5 */ -#define FSL_SAI_RFR6 0xd8 /* SAI Receive FIFO 6 */ -#define FSL_SAI_RFR7 0xdc /* SAI Receive FIFO 7 */ +#define FSL_SAI_TTCTL 0x70 /* SAI Transmit Timestamp Control Register */ +#define FSL_SAI_TTCTN 0x74 /* SAI Transmit Timestamp Counter Register */ +#define FSL_SAI_TBCTN 0x78 /* SAI Transmit Bit Counter Register */ +#define FSL_SAI_TTCAP 0x7C /* SAI Transmit Timestamp Capture */ + +#define FSL_SAI_RCSR(offset) (0x80 + offset) /* SAI Receive Control */ +#define FSL_SAI_RCR1(offset) (0x84 + offset) /* SAI Receive Configuration 1 */ +#define FSL_SAI_RCR2(offset) (0x88 + offset) /* SAI Receive Configuration 2 */ +#define FSL_SAI_RCR3(offset) (0x8c + offset) /* SAI Receive Configuration 3 */ +#define FSL_SAI_RCR4(offset) (0x90 + offset) /* SAI Receive Configuration 4 */ +#define FSL_SAI_RCR5(offset) (0x94 + offset) /* SAI Receive Configuration 5 */ +#define FSL_SAI_RDR0 0xa0 /* SAI Receive Data */ +#define FSL_SAI_RDR1 0xa4 /* SAI Receive Data */ +#define FSL_SAI_RDR2 0xa8 /* SAI Receive Data */ +#define FSL_SAI_RDR3 0xac /* SAI Receive Data */ +#define FSL_SAI_RDR4 0xb0 /* SAI Receive Data */ +#define FSL_SAI_RDR5 0xb4 /* SAI Receive Data */ +#define FSL_SAI_RDR6 0xb8 /* SAI Receive Data */ +#define FSL_SAI_RDR7 0xbc /* SAI Receive Data */ +#define FSL_SAI_RFR0 0xc0 /* SAI Receive FIFO */ +#define FSL_SAI_RFR1 0xc4 /* SAI Receive FIFO */ +#define FSL_SAI_RFR2 0xc8 /* SAI Receive FIFO */ +#define FSL_SAI_RFR3 0xcc /* SAI Receive FIFO */ +#define FSL_SAI_RFR4 0xd0 /* SAI Receive FIFO */ +#define FSL_SAI_RFR5 0xd4 /* SAI Receive FIFO */ +#define FSL_SAI_RFR6 0xd8 /* SAI Receive FIFO */ +#define FSL_SAI_RFR7 0xdc /* SAI Receive FIFO */ #define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */ +#define FSL_SAI_RTCTL 0xf0 /* SAI Receive Timestamp Control Register */ +#define FSL_SAI_RTCTN 0xf4 /* SAI Receive Timestamp Counter Register */ +#define FSL_SAI_RBCTN 0xf8 /* SAI Receive Bit Counter Register */ +#define FSL_SAI_RTCAP 0xfc /* SAI Receive Timestamp Capture */ + +#define FSL_SAI_MCTL 0x100 /* SAI MCLK Control Register */ +#define FSL_SAI_MDIV 0x104 /* SAI MCLK Divide Register */ -#define FSL_SAI_xCSR(tx, ofs) (tx ? FSL_SAI_TCSR(ofs) : FSL_SAI_RCSR(ofs)) -#define FSL_SAI_xCR1(tx, ofs) (tx ? FSL_SAI_TCR1(ofs) : FSL_SAI_RCR1(ofs)) -#define FSL_SAI_xCR2(tx, ofs) (tx ? FSL_SAI_TCR2(ofs) : FSL_SAI_RCR2(ofs)) -#define FSL_SAI_xCR3(tx, ofs) (tx ? FSL_SAI_TCR3(ofs) : FSL_SAI_RCR3(ofs)) -#define FSL_SAI_xCR4(tx, ofs) (tx ? FSL_SAI_TCR4(ofs) : FSL_SAI_RCR4(ofs)) -#define FSL_SAI_xCR5(tx, ofs) (tx ? FSL_SAI_TCR5(ofs) : FSL_SAI_RCR5(ofs)) -#define FSL_SAI_xDR(tx, ofs) (tx ? FSL_SAI_TDR(ofs) : FSL_SAI_RDR(ofs)) -#define FSL_SAI_xFR(tx, ofs) (tx ? FSL_SAI_TFR(ofs) : FSL_SAI_RFR(ofs)) +#define FSL_SAI_xCSR(tx, off) (tx ? FSL_SAI_TCSR(off) : FSL_SAI_RCSR(off)) +#define FSL_SAI_xCR1(tx, off) (tx ? FSL_SAI_TCR1(off) : FSL_SAI_RCR1(off)) +#define FSL_SAI_xCR2(tx, off) (tx ? FSL_SAI_TCR2(off) : FSL_SAI_RCR2(off)) +#define FSL_SAI_xCR3(tx, off) (tx ? FSL_SAI_TCR3(off) : FSL_SAI_RCR3(off)) +#define FSL_SAI_xCR4(tx, off) (tx ? FSL_SAI_TCR4(off) : FSL_SAI_RCR4(off)) +#define FSL_SAI_xCR5(tx, off) (tx ? FSL_SAI_TCR5(off) : FSL_SAI_RCR5(off)) #define FSL_SAI_xMR(tx) (tx ? FSL_SAI_TMR : FSL_SAI_RMR) /* SAI Transmit/Receive Control Register */ #define FSL_SAI_CSR_TERE BIT(31) +#define FSL_SAI_CSR_SE BIT(30) #define FSL_SAI_CSR_FR BIT(25) #define FSL_SAI_CSR_SR BIT(24) #define FSL_SAI_CSR_xF_SHIFT 16 @@ -106,19 +122,29 @@ #define FSL_SAI_CR2_MSEL(ID) ((ID) << 26) #define FSL_SAI_CR2_BCP BIT(25) #define FSL_SAI_CR2_BCD_MSTR BIT(24) +#define FSL_SAI_CR2_BYP BIT(23) /* BCLK bypass */ #define FSL_SAI_CR2_DIV_MASK 0xff /* SAI Transmit and Receive Configuration 3 Register */ -#define FSL_SAI_CR3_TRCE BIT(16) -#define FSL_SAI_CR3_TRCE_MASK GENMASK(23, 16) +#define FSL_SAI_CR3_TRCE_MASK (0xff << 16) +#define FSL_SAI_CR3_TRCE(x) (x << 16) #define FSL_SAI_CR3_WDFL(x) (x) #define FSL_SAI_CR3_WDFL_MASK 0x1f /* SAI Transmit and Receive Configuration 4 Register */ + +#define FSL_SAI_CR4_FCONT BIT(28) +#define FSL_SAI_CR4_FCOMB_SHIFT BIT(26) +#define FSL_SAI_CR4_FCOMB_SOFT BIT(27) +#define FSL_SAI_CR4_FCOMB_MASK (0x3 << 26) +#define FSL_SAI_CR4_FPACK_8 (0x2 << 24) +#define FSL_SAI_CR4_FPACK_16 (0x3 << 24) #define FSL_SAI_CR4_FRSZ(x) (((x) - 1) << 16) #define FSL_SAI_CR4_FRSZ_MASK (0x1f << 16) #define FSL_SAI_CR4_SYWD(x) (((x) - 1) << 8) #define FSL_SAI_CR4_SYWD_MASK (0x1f << 8) +#define FSL_SAI_CR4_CHMOD (1 << 5) +#define FSL_SAI_CR4_CHMOD_MASK (1 << 5) #define FSL_SAI_CR4_MF BIT(4) #define FSL_SAI_CR4_FSE BIT(3) #define FSL_SAI_CR4_FSP BIT(1) @@ -132,6 +158,33 @@ #define FSL_SAI_CR5_FBT(x) ((x) << 8) #define FSL_SAI_CR5_FBT_MASK (0x1f << 8) +/* SAI MCLK Control Register */ +#define FSL_SAI_MCTL_MCLK_EN BIT(30) /* MCLK Enable */ +#define FSL_SAI_MCTL_MSEL_MASK (0x3 << 24) +#define FSL_SAI_MCTL_MSEL(ID) ((ID) << 24) +#define FSL_SAI_MCTL_MSEL_BUS 0 +#define FSL_SAI_MCTL_MSEL_MCLK1 BIT(24) +#define FSL_SAI_MCTL_MSEL_MCLK2 BIT(25) +#define FSL_SAI_MCTL_MSEL_MCLK3 (BIT(24) | BIT(25)) +#define FSL_SAI_MCTL_DIV_EN BIT(23) +#define FSL_SAI_MCTL_DIV_MASK 0xFF + +/* SAI VERID Register */ +#define FSL_SAI_VER_ID_SHIFT 16 +#define FSL_SAI_VER_ID_MASK (0xFFFF << FSL_SAI_VER_ID_SHIFT) +#define FSL_SAI_VER_EFIFO_EN BIT(0) +#define FSL_SAI_VER_TSTMP_EN BIT(1) + +/* SAI PARAM Register */ +#define FSL_SAI_PAR_SPF_SHIFT 16 +#define FSL_SAI_PAR_SPF_MASK (0x0F << FSL_SAI_PAR_SPF_SHIFT) +#define FSL_SAI_PAR_WPF_SHIFT 8 +#define FSL_SAI_PAR_WPF_MASK (0x0F << FSL_SAI_PAR_WPF_SHIFT) +#define FSL_SAI_PAR_DLN_MASK (0x0F) + +/* SAI MCLK Divide Register */ +#define FSL_SAI_MDIV_MASK 0xFFFFF + /* SAI type */ #define FSL_SAI_DMA BIT(0) #define FSL_SAI_USE_AC97 BIT(1) @@ -155,36 +208,92 @@ #define FSL_SAI_MAXBURST_TX 6 #define FSL_SAI_MAXBURST_RX 6 +#define SAI_FLAG_PMQOS BIT(0) + +/* SAI timestamp and bitcounter */ +#define FSL_SAI_xTCTL_TSEN BIT(0) +#define FSL_SAI_xTCTL_TSINC BIT(1) +#define FSL_SAI_xTCTL_RTSC BIT(8) +#define FSL_SAI_xTCTL_RBC BIT(9) + struct fsl_sai_soc_data { - bool use_imx_pcm; - bool use_edma; unsigned int fifo_depth; - unsigned int reg_offset; + unsigned int fifos; + unsigned int dataline; + unsigned int flags; + unsigned char reg_offset; + bool imx; + /* True for EDMA because it needs period size multiple of maxburst */ + bool constrain_period_size; +}; + +struct fsl_sai_verid { + u32 id; + bool timestamp_en; + bool extfifo_en; + bool loaded; +}; + +struct fsl_sai_param { + u32 spf; /* max slots per frame */ + u32 wpf; /* words in fifo */ + u32 dln; /* number of datalines implemented */ +}; + +struct fsl_sai_dl_cfg { + unsigned int pins; + unsigned int mask[2]; + unsigned int offset[2]; }; struct fsl_sai { struct platform_device *pdev; struct regmap *regmap; + struct regmap *regmap_gpr; struct clk *bus_clk; struct clk *mclk_clk[FSL_SAI_MCLK_MAX]; + struct clk *pll8k_clk; + struct clk *pll11k_clk; - bool is_slave_mode; + bool slave_mode[2]; bool is_lsb_first; bool is_dsp_mode; + bool is_multi_lane; bool synchronous[2]; + bool is_stream_opened[2]; + bool is_dsd; + bool monitor_spdif; + bool monitor_spdif_start; + + int gpr_idx; + int pcm_dl_cfg_cnt; + int dsd_dl_cfg_cnt; + struct fsl_sai_dl_cfg *pcm_dl_cfg; + struct fsl_sai_dl_cfg *dsd_dl_cfg; + + unsigned int masterflag[2]; unsigned int mclk_id[2]; unsigned int mclk_streams; unsigned int slots; unsigned int slot_width; - unsigned int bclk_ratio; + unsigned int bitclk_ratio; const struct fsl_sai_soc_data *soc_data; struct snd_soc_dai_driver cpu_dai_drv; struct snd_dmaengine_dai_dma_data dma_params_rx; struct snd_dmaengine_dai_dma_data dma_params_tx; + const struct fsl_sai_soc_data *soc; + struct pm_qos_request pm_qos_req; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_state; + + struct fsl_sai_verid verid; + struct fsl_sai_param param; }; +const struct attribute_group *fsl_sai_get_dev_attribute_group(bool monitor_spdif); + #define TX 1 #define RX 0 diff --git a/sound/soc/fsl/fsl_sai_sysfs.c b/sound/soc/fsl/fsl_sai_sysfs.c new file mode 100644 index 000000000000..920ebcccc427 --- /dev/null +++ b/sound/soc/fsl/fsl_sai_sysfs.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2020 NXP Semiconductors. + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/kdev_t.h> +#include <linux/pm_runtime.h> +#include <linux/mfd/syscon/imx7-iomuxc-gpr.h> + +#include "fsl_sai.h" + +static ssize_t tx_bitcnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read bitcounter */ + regmap_read(sai->regmap, FSL_SAI_TBCTN, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(tx_bitcnt); + +static ssize_t tx_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read timestamp */ + regmap_read(sai->regmap, FSL_SAI_TTCTN, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(tx_timestamp); + +static ssize_t tx_bitcnt_latched_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read timestamp */ + regmap_read(sai->regmap, FSL_SAI_TTCAP, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(tx_bitcnt_latched_timestamp); + +static ssize_t +tx_timestamp_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_TSEN; + regmap_update_bits(sai->regmap, FSL_SAI_TTCTL, FSL_SAI_xTCTL_TSEN, val); + return n; +} +static DEVICE_ATTR_WO(tx_timestamp_enable); + +static ssize_t +tx_timestamp_increment_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_TSINC; + regmap_update_bits(sai->regmap, FSL_SAI_TTCTL, FSL_SAI_xTCTL_TSINC, val); + return n; +} +static DEVICE_ATTR_WO(tx_timestamp_increment); + +static ssize_t +tx_bitcnt_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_RBC; + regmap_update_bits(sai->regmap, FSL_SAI_TTCTL, FSL_SAI_xTCTL_RBC, val); + return n; +} +static DEVICE_ATTR_WO(tx_bitcnt_reset); + +static ssize_t +tx_timestamp_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_RTSC; + regmap_update_bits(sai->regmap, FSL_SAI_TTCTL, FSL_SAI_xTCTL_RTSC, val); + return n; +} +static DEVICE_ATTR_WO(tx_timestamp_reset); + + +static ssize_t rx_bitcnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read bitcounter */ + regmap_read(sai->regmap, FSL_SAI_RBCTN, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(rx_bitcnt); + +static ssize_t rx_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read timestamp */ + regmap_read(sai->regmap, FSL_SAI_RTCTN, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(rx_timestamp); + +static ssize_t rx_bitcnt_latched_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read timestamp */ + regmap_read(sai->regmap, FSL_SAI_RTCAP, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(rx_bitcnt_latched_timestamp); + +static ssize_t +rx_timestamp_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_TSEN; + regmap_update_bits(sai->regmap, FSL_SAI_RTCTL, FSL_SAI_xTCTL_TSEN, val); + return n; +} +static DEVICE_ATTR_WO(rx_timestamp_enable); + +static ssize_t +rx_timestamp_increment_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_TSINC; + regmap_update_bits(sai->regmap, FSL_SAI_RTCTL, FSL_SAI_xTCTL_TSINC, val); + return n; +} +static DEVICE_ATTR_WO(rx_timestamp_increment); + +static ssize_t +rx_bitcnt_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_RBC; + regmap_update_bits(sai->regmap, FSL_SAI_RTCTL, FSL_SAI_xTCTL_RBC, val); + return n; +} +static DEVICE_ATTR_WO(rx_bitcnt_reset); + +static ssize_t +rx_timestamp_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_RTSC; + regmap_update_bits(sai->regmap, FSL_SAI_RTCTL, FSL_SAI_xTCTL_RTSC, val); + return n; +} +static DEVICE_ATTR_WO(rx_timestamp_reset); + +static ssize_t +rx_monitor_spdif_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned char offset = sai->soc->reg_offset; + unsigned int val = 0; + bool enable = false; + unsigned int reg; + unsigned int shift; + unsigned int mask; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + enable = true; + + if (pm_runtime_active(&sai->pdev->dev) && enable) { + dev_err(dev, "device is busy\n"); + return -EBUSY; + } + + reg = IOMUXC_GPR6 + (sai->gpr_idx - 1) / 2 * 4; + shift = ((sai->gpr_idx - 1) % 2) * 16; + mask = 0x1F << shift; + + if (enable) { + pm_runtime_get_sync(&sai->pdev->dev); + /* Fix to MCLK3 */ + regmap_update_bits(sai->regmap_gpr, reg, mask, 0xF << shift); + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(false, offset), + FSL_SAI_CR2_MSEL_MASK, FSL_SAI_CR2_MSEL(0x3)); + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(false, offset), + FSL_SAI_CR2_DIV_MASK, 0x0); + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(false, offset), + FSL_SAI_CR2_BCD_MSTR, FSL_SAI_CR2_BCD_MSTR); + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(false, offset), + FSL_SAI_CR4_FSD_MSTR, FSL_SAI_CR4_FSD_MSTR); + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(false, offset), + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + sai->monitor_spdif_start = true; + } else { + if (sai->monitor_spdif_start) { + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(false, offset), + FSL_SAI_CSR_TERE, 0); + pm_runtime_put_sync(&sai->pdev->dev); + sai->monitor_spdif_start = false; + } + } + + return n; +} +static DEVICE_ATTR_WO(rx_monitor_spdif); + +static struct attribute *fsl_sai_attrs[] = { + &dev_attr_tx_bitcnt.attr, + &dev_attr_rx_bitcnt.attr, + &dev_attr_tx_timestamp.attr, + &dev_attr_rx_timestamp.attr, + &dev_attr_tx_bitcnt_latched_timestamp.attr, + &dev_attr_rx_bitcnt_latched_timestamp.attr, + &dev_attr_tx_timestamp_enable.attr, + &dev_attr_rx_timestamp_enable.attr, + &dev_attr_tx_timestamp_increment.attr, + &dev_attr_rx_timestamp_increment.attr, + &dev_attr_tx_bitcnt_reset.attr, + &dev_attr_rx_bitcnt_reset.attr, + &dev_attr_tx_timestamp_reset.attr, + &dev_attr_rx_timestamp_reset.attr, + NULL, + NULL, +}; + +static struct attribute_group fsl_sai_attr_group = { + .attrs = fsl_sai_attrs, +}; + +const struct attribute_group *fsl_sai_get_dev_attribute_group(bool monitor_spdif) +{ + if (monitor_spdif) + fsl_sai_attrs[ARRAY_SIZE(fsl_sai_attrs) - 2] = &dev_attr_rx_monitor_spdif.attr; + + return &fsl_sai_attr_group; +} diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c index 7858a5499ac5..49f5a8551143 100644 --- a/sound/soc/fsl/fsl_spdif.c +++ b/sound/soc/fsl/fsl_spdif.c @@ -2,7 +2,7 @@ // // Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver // -// Copyright (C) 2013 Freescale Semiconductor, Inc. +// Copyright (C) 2013-2016 Freescale Semiconductor, Inc. // // Based on stmp3xxx_spdif_dai.c // Vladimir Barinov <vbarinov@embeddedalley.com> @@ -11,11 +11,14 @@ #include <linux/bitrev.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/module.h> #include <linux/of_address.h> #include <linux/of_device.h> #include <linux/of_irq.h> #include <linux/regmap.h> +#include <linux/pm_runtime.h> +#include <linux/busfreq-imx.h> #include <sound/asoundef.h> #include <sound/dmaengine_pcm.h> @@ -42,6 +45,16 @@ static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb }; #define DEFAULT_RXCLK_SRC 1 +struct fsl_spdif_soc_data { + bool imx; + bool constrain_period_size; + u32 tx_burst; + u32 rx_burst; + u32 interrupts; + u64 tx_formats; + u64 rx_rates; +}; + /* * SPDIF control structure * Defines channel status, subcode and Q sub @@ -99,15 +112,59 @@ struct fsl_spdif_priv { u16 sysclk_df[SPDIF_TXRATE_MAX]; u8 txclk_src[SPDIF_TXRATE_MAX]; u8 rxclk_src; - struct clk *txclk[SPDIF_TXRATE_MAX]; + struct clk *txclk[STC_TXCLK_SRC_MAX]; struct clk *rxclk; struct clk *coreclk; struct clk *sysclk; struct clk *spbaclk; + const struct fsl_spdif_soc_data *soc; struct snd_dmaengine_dai_dma_data dma_params_tx; struct snd_dmaengine_dai_dma_data dma_params_rx; /* regcache for SRPC */ u32 regcache_srpc; + struct clk *pll8k_clk; + struct clk *pll11k_clk; +}; + +static struct fsl_spdif_soc_data fsl_spdif_vf610 = { + .imx = false, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = FSL_SPDIF_RATES_CAPTURE, + .constrain_period_size = false, +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx35 = { + .imx = true, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = FSL_SPDIF_RATES_CAPTURE, + .constrain_period_size = false, +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx8qm = { + .imx = true, + .tx_burst = 2, + .rx_burst = 2, + .interrupts = 2, + .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, + .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000), + .constrain_period_size = true, +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx8mm = { + .imx = true, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000), + .constrain_period_size = false, }; /* DPLL locked and lock loss interrupt handler */ @@ -378,7 +435,6 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, u32 stc, mask, rate; u16 sysclk_df; u8 clk, txclk_df; - int ret; switch (sample_rate) { case 32000: @@ -393,10 +449,18 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, rate = SPDIF_TXRATE_48000; csfs = IEC958_AES3_CON_FS_48000; break; + case 88200: + rate = SPDIF_TXRATE_88200; + csfs = IEC958_AES3_CON_FS_88200; + break; case 96000: rate = SPDIF_TXRATE_96000; csfs = IEC958_AES3_CON_FS_96000; break; + case 176400: + rate = SPDIF_TXRATE_176400; + csfs = IEC958_AES3_CON_FS_176400; + break; case 192000: rate = SPDIF_TXRATE_192000; csfs = IEC958_AES3_CON_FS_192000; @@ -420,23 +484,10 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, sysclk_df = spdif_priv->sysclk_df[rate]; - /* Don't mess up the clocks from other modules */ - if (clk != STC_TXCLK_SPDIF_ROOT) - goto clk_set_bypass; - - /* The S/PDIF block needs a clock of 64 * fs * txclk_df */ - ret = clk_set_rate(spdif_priv->txclk[rate], - 64 * sample_rate * txclk_df); - if (ret) { - dev_err(&pdev->dev, "failed to set tx clock rate\n"); - return ret; - } - -clk_set_bypass: dev_dbg(&pdev->dev, "expected clock rate = %d\n", (64 * sample_rate * txclk_df * sysclk_df)); dev_dbg(&pdev->dev, "actual clock rate = %ld\n", - clk_get_rate(spdif_priv->txclk[rate])); + clk_get_rate(spdif_priv->txclk[clk])); /* set fs field in consumer channel status */ spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs); @@ -462,25 +513,10 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream, struct platform_device *pdev = spdif_priv->pdev; struct regmap *regmap = spdif_priv->regmap; u32 scr, mask; - int i; int ret; /* Reset module and interrupts only for first initialization */ if (!cpu_dai->active) { - ret = clk_prepare_enable(spdif_priv->coreclk); - if (ret) { - dev_err(&pdev->dev, "failed to enable core clock\n"); - return ret; - } - - if (!IS_ERR(spdif_priv->spbaclk)) { - ret = clk_prepare_enable(spdif_priv->spbaclk); - if (ret) { - dev_err(&pdev->dev, "failed to enable spba clock\n"); - goto err_spbaclk; - } - } - ret = spdif_softreset(spdif_priv); if (ret) { dev_err(&pdev->dev, "failed to soft reset\n"); @@ -498,35 +534,30 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream, mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | SCR_TXFIFO_FSEL_MASK; - for (i = 0; i < SPDIF_TXRATE_MAX; i++) { - ret = clk_prepare_enable(spdif_priv->txclk[i]); - if (ret) - goto disable_txclk; - } } else { scr = SCR_RXFIFO_FSEL_IF8 | SCR_RXFIFO_AUTOSYNC; mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; - ret = clk_prepare_enable(spdif_priv->rxclk); - if (ret) - goto err; } regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); /* Power up SPDIF module */ regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, 0); + if (spdif_priv->soc->constrain_period_size) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + spdif_priv->dma_params_tx.maxburst); + else + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + spdif_priv->dma_params_rx.maxburst); + } + return 0; -disable_txclk: - for (i--; i >= 0; i--) - clk_disable_unprepare(spdif_priv->txclk[i]); err: - if (!IS_ERR(spdif_priv->spbaclk)) - clk_disable_unprepare(spdif_priv->spbaclk); -err_spbaclk: - clk_disable_unprepare(spdif_priv->coreclk); - return ret; } @@ -536,20 +567,17 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); struct regmap *regmap = spdif_priv->regmap; - u32 scr, mask, i; + u32 scr, mask; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { scr = 0; mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | SCR_TXFIFO_FSEL_MASK; - for (i = 0; i < SPDIF_TXRATE_MAX; i++) - clk_disable_unprepare(spdif_priv->txclk[i]); } else { scr = SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO; mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; - clk_disable_unprepare(spdif_priv->rxclk); } regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); @@ -558,9 +586,6 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream, spdif_intr_status_clear(spdif_priv); regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, SCR_LOW_POWER); - if (!IS_ERR(spdif_priv->spbaclk)) - clk_disable_unprepare(spdif_priv->spbaclk); - clk_disable_unprepare(spdif_priv->coreclk); } } @@ -623,14 +648,180 @@ static int fsl_spdif_trigger(struct snd_pcm_substream *substream, return 0; } +static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, + struct clk *clk, u64 savesub, + enum spdif_txrate index, bool round) +{ + static const u32 rate[] = { 32000, 44100, 48000, 88200, 96000, 176400, + 192000, }; + bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); + u64 rate_actual, sub; + u32 arate; + u16 sysclk_dfmin, sysclk_dfmax, sysclk_df; + u8 txclk_df; + + /* The sysclk has an extra divisor [2, 512] */ + sysclk_dfmin = is_sysclk ? 2 : 1; + sysclk_dfmax = is_sysclk ? 512 : 1; + + for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) { + for (txclk_df = 1; txclk_df <= 128; txclk_df++) { + + rate_actual = clk_get_rate(clk); + + arate = rate_actual / 64; + arate /= txclk_df * sysclk_df; + + if (arate == rate[index]) { + /* We are lucky */ + savesub = 0; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + goto out; + } else if (arate / rate[index] == 1) { + /* A little bigger than expect */ + sub = (u64)(arate - rate[index]) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } else if (rate[index] / arate == 1) { + /* A little smaller than expect */ + sub = (u64)(rate[index] - arate) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } + } + } + +out: + return savesub; +} + +static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, + enum spdif_txrate index) +{ + static const u32 rate[] = { 32000, 44100, 48000, 88200, 96000, 176400, + 192000, }; + struct platform_device *pdev = spdif_priv->pdev; + struct device *dev = &pdev->dev; + u64 savesub = 100000, ret; + struct clk *clk; + int i; + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + clk = spdif_priv->txclk[i]; + if (IS_ERR(clk)) { + dev_err(dev, "no rxtx%d clock in devicetree\n", i); + return PTR_ERR(clk); + } + if (!clk_get_rate(clk)) + continue; + + ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index, + i == STC_TXCLK_SPDIF_ROOT); + if (savesub == ret) + continue; + + savesub = ret; + spdif_priv->txclk_src[index] = i; + + /* To quick catch a divisor, we allow a 0.1% deviation */ + if (savesub < 100) + break; + } + + dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n", + spdif_priv->txclk_src[index], rate[index]); + dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n", + spdif_priv->txclk_df[index], rate[index]); + if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk)) + dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n", + spdif_priv->sysclk_df[index], rate[index]); + dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n", + rate[index], spdif_priv->txrate[index]); + + return 0; +} + +static int fsl_spdif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct fsl_spdif_priv *data = snd_soc_dai_get_drvdata(cpu_dai); + struct platform_device *pdev = data->pdev; + struct device *dev = &pdev->dev; + struct clk *clk, *p, *pll = 0, *npll = 0; + u64 ratio = freq; + int ret, i; + bool reparent = false; + + if (dir != SND_SOC_CLOCK_OUT || freq == 0 || clk_id != STC_TXCLK_SPDIF_ROOT) + return 0; + + if (data->pll8k_clk == NULL || data->pll11k_clk == NULL) + return 0; + + clk = data->txclk[clk_id]; + if (IS_ERR_OR_NULL(clk)) { + dev_err(dev, "no rxtx%d clock in devicetree\n", clk_id); + return PTR_ERR(clk); + } + + p = clk; + while (p && data->pll8k_clk && data->pll11k_clk) { + struct clk *pp = clk_get_parent(p); + + if (clk_is_match(pp, data->pll8k_clk) || + clk_is_match(pp, data->pll11k_clk)) { + pll = pp; + break; + } + p = pp; + } + + npll = (do_div(ratio, 8000) ? data->pll11k_clk : data->pll8k_clk); + reparent = (pll && !clk_is_match(pll, npll)); + + clk_disable_unprepare(clk); + if (reparent) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(cpu_dai->dev, "failed to set parent %s: %d\n", + __clk_get_name(npll), ret); + } + + ret = clk_set_rate(clk, freq); + if (ret < 0) + dev_warn(cpu_dai->dev, "failed to set clock rate (%u): %d\n", + freq, ret); + clk_prepare_enable(clk); + + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { + ret = fsl_spdif_probe_txclk(data, i); + if (ret) + return ret; + } + + return 0; +} + static const struct snd_soc_dai_ops fsl_spdif_dai_ops = { .startup = fsl_spdif_startup, + .set_sysclk = fsl_spdif_set_dai_sysclk, .hw_params = fsl_spdif_hw_params, .trigger = fsl_spdif_trigger, .shutdown = fsl_spdif_shutdown, }; - /* * FSL SPDIF IEC958 controller(mixer) functions * @@ -769,19 +960,23 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, } /* Valid bit information */ -static int fsl_spdif_vbit_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) +/* Get valid good bit from interrupt status register */ +static int fsl_spdif_rx_vbit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - uinfo->count = 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SIS, &val); + ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0; + regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD); return 0; } -/* Get valid good bit from interrupt status register */ -static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol, +static int fsl_spdif_tx_vbit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); @@ -789,9 +984,56 @@ static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol, struct regmap *regmap = spdif_priv->regmap; u32 val; - regmap_read(regmap, REG_SPDIF_SIS, &val); - ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0; - regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD); + regmap_read(regmap, REG_SPDIF_SCR, &val); + val = (val & SCR_VAL_MASK) >> SCR_VAL_OFFSET; + val = 1 - val; + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int fsl_spdif_tx_vbit_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val = (1 - ucontrol->value.integer.value[0]) << SCR_VAL_OFFSET; + + regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_VAL_MASK, val); + + return 0; +} + +static int fsl_spdif_rx_rcm_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SCR, &val); + val = (val & SCR_RAW_CAPTURE_MODE) ? 1 : 0; + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int fsl_spdif_rx_rcm_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val = (ucontrol->value.integer.value[0] ? SCR_RAW_CAPTURE_MODE : 0); + + if (val) + cpu_dai->driver->capture.formats |= SNDRV_PCM_FMTBIT_S32_LE; + else + cpu_dai->driver->capture.formats &= ~SNDRV_PCM_FMTBIT_S32_LE; + + regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_RAW_CAPTURE_MODE, val); return 0; } @@ -863,18 +1105,6 @@ static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol, return 0; } -/* User bit sync mode info */ -static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - uinfo->count = 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; - - return 0; -} - /* * User bit sync mode: * 1 CD User channel subcode @@ -953,11 +1183,21 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { /* Valid bit error controller */ { .iface = SNDRV_CTL_ELEM_IFACE_PCM, - .name = "IEC958 V-Bit Errors", + .name = "IEC958 Rx V-Bit Errors", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .info = fsl_spdif_vbit_info, - .get = fsl_spdif_vbit_get, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_rx_vbit_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Tx V-Bit", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_tx_vbit_get, + .put = fsl_spdif_tx_vbit_put, }, /* DPLL lock info get controller */ { @@ -975,10 +1215,20 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .info = fsl_spdif_usync_info, + .info = snd_ctl_boolean_mono_info, .get = fsl_spdif_usync_get, .put = fsl_spdif_usync_put, }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Rx Raw Capture Mode Bit", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_rx_rcm_get, + .put = fsl_spdif_rx_rcm_put, + }, }; static int fsl_spdif_dai_probe(struct snd_soc_dai *dai) @@ -1103,114 +1353,14 @@ static const struct regmap_config fsl_spdif_regmap_config = { .cache_type = REGCACHE_FLAT, }; -static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, - struct clk *clk, u64 savesub, - enum spdif_txrate index, bool round) -{ - static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; - bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); - u64 rate_ideal, rate_actual, sub; - u32 arate; - u16 sysclk_dfmin, sysclk_dfmax, sysclk_df; - u8 txclk_df; - - /* The sysclk has an extra divisor [2, 512] */ - sysclk_dfmin = is_sysclk ? 2 : 1; - sysclk_dfmax = is_sysclk ? 512 : 1; - - for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) { - for (txclk_df = 1; txclk_df <= 128; txclk_df++) { - rate_ideal = rate[index] * txclk_df * 64ULL; - if (round) - rate_actual = clk_round_rate(clk, rate_ideal); - else - rate_actual = clk_get_rate(clk); - - arate = rate_actual / 64; - arate /= txclk_df * sysclk_df; - - if (arate == rate[index]) { - /* We are lucky */ - savesub = 0; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - goto out; - } else if (arate / rate[index] == 1) { - /* A little bigger than expect */ - sub = (u64)(arate - rate[index]) * 100000; - do_div(sub, rate[index]); - if (sub >= savesub) - continue; - savesub = sub; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - } else if (rate[index] / arate == 1) { - /* A little smaller than expect */ - sub = (u64)(rate[index] - arate) * 100000; - do_div(sub, rate[index]); - if (sub >= savesub) - continue; - savesub = sub; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - } - } - } - -out: - return savesub; -} - -static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, - enum spdif_txrate index) -{ - static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; - struct platform_device *pdev = spdif_priv->pdev; - struct device *dev = &pdev->dev; - u64 savesub = 100000, ret; - struct clk *clk; - char tmp[16]; - int i; - - for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { - sprintf(tmp, "rxtx%d", i); - clk = devm_clk_get(&pdev->dev, tmp); - if (IS_ERR(clk)) { - dev_err(dev, "no rxtx%d clock in devicetree\n", i); - return PTR_ERR(clk); - } - if (!clk_get_rate(clk)) - continue; - - ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index, - i == STC_TXCLK_SPDIF_ROOT); - if (savesub == ret) - continue; - - savesub = ret; - spdif_priv->txclk[index] = clk; - spdif_priv->txclk_src[index] = i; - - /* To quick catch a divisor, we allow a 0.1% deviation */ - if (savesub < 100) - break; - } - - dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n", - spdif_priv->txclk_src[index], rate[index]); - dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n", - spdif_priv->txclk_df[index], rate[index]); - if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk)) - dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n", - spdif_priv->sysclk_df[index], rate[index]); - dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n", - rate[index], spdif_priv->txrate[index]); - - return 0; -} +static const struct of_device_id fsl_spdif_dt_ids[] = { + { .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, }, + { .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, }, + { .compatible = "fsl,imx35-spdif", .data = &fsl_spdif_imx35, }, + { .compatible = "fsl,vf610-spdif", .data = &fsl_spdif_vf610, }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); static int fsl_spdif_probe(struct platform_device *pdev) { @@ -1218,8 +1368,10 @@ static int fsl_spdif_probe(struct platform_device *pdev) struct fsl_spdif_priv *spdif_priv; struct spdif_mixer_control *ctrl; struct resource *res; + const struct of_device_id *of_id; void __iomem *regs; int irq, ret, i; + char tmp[16]; if (!np) return -ENODEV; @@ -1230,9 +1382,19 @@ static int fsl_spdif_probe(struct platform_device *pdev) spdif_priv->pdev = pdev; + of_id = of_match_device(fsl_spdif_dt_ids, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + spdif_priv->soc = of_id->data; + /* Initialize this copy of the CPU DAI driver structure */ memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai)); spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev); + spdif_priv->cpu_dai_drv.playback.formats = + spdif_priv->soc->tx_formats; + spdif_priv->cpu_dai_drv.capture.rates = + spdif_priv->soc->rx_rates; /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1241,15 +1403,17 @@ static int fsl_spdif_probe(struct platform_device *pdev) return PTR_ERR(regs); spdif_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "core", regs, &fsl_spdif_regmap_config); + NULL, regs, &fsl_spdif_regmap_config); if (IS_ERR(spdif_priv->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); return PTR_ERR(spdif_priv->regmap); } irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); return irq; + } ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0, dev_name(&pdev->dev), spdif_priv); @@ -1258,8 +1422,32 @@ static int fsl_spdif_probe(struct platform_device *pdev) return ret; } + if (spdif_priv->soc->interrupts > 1) { + irq = platform_get_irq(pdev, 1); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0, + dev_name(&pdev->dev), spdif_priv); + if (ret) { + dev_err(&pdev->dev, "could not claim irq %u\n", irq); + return ret; + } + } + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + sprintf(tmp, "rxtx%d", i); + spdif_priv->txclk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(spdif_priv->txclk[i])) { + dev_err(&pdev->dev, "no rxtx%d clock in devicetree\n", i); + return PTR_ERR(spdif_priv->txclk[i]); + } + } + /* Get system clock for rx clock rate calculation */ - spdif_priv->sysclk = devm_clk_get(&pdev->dev, "rxtx5"); + spdif_priv->sysclk = spdif_priv->txclk[5]; if (IS_ERR(spdif_priv->sysclk)) { dev_err(&pdev->dev, "no sys clock (rxtx5) in devicetree\n"); return PTR_ERR(spdif_priv->sysclk); @@ -1277,13 +1465,21 @@ static int fsl_spdif_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "no spba clock in devicetree\n"); /* Select clock source for rx/tx clock */ - spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1"); + spdif_priv->rxclk = spdif_priv->txclk[1]; if (IS_ERR(spdif_priv->rxclk)) { dev_err(&pdev->dev, "no rxtx1 clock in devicetree\n"); return PTR_ERR(spdif_priv->rxclk); } spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC; + spdif_priv->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(spdif_priv->pll8k_clk)) + spdif_priv->pll8k_clk = NULL; + + spdif_priv->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(spdif_priv->pll11k_clk)) + spdif_priv->pll11k_clk = NULL; + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { ret = fsl_spdif_probe_txclk(spdif_priv, i); if (ret) @@ -1304,11 +1500,24 @@ static int fsl_spdif_probe(struct platform_device *pdev) spdif_priv->dpll_locked = false; - spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML; - spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML; + spdif_priv->dma_params_tx.maxburst = spdif_priv->soc->tx_burst; + spdif_priv->dma_params_rx.maxburst = spdif_priv->soc->rx_burst; spdif_priv->dma_params_tx.addr = res->start + REG_SPDIF_STL; spdif_priv->dma_params_rx.addr = res->start + REG_SPDIF_SRL; + ret = clk_prepare_enable(spdif_priv->coreclk); + if (ret) + return ret; + + /*Cleer the val bit for Tx*/ + regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SCR, + SCR_VAL_MASK, 1 << SCR_VAL_OFFSET); + + clk_disable_unprepare(spdif_priv->coreclk); + + pm_runtime_enable(&pdev->dev); + regcache_cache_only(spdif_priv->regmap, true); + /* Register with ASoC */ dev_set_drvdata(&pdev->dev, spdif_priv); @@ -1326,44 +1535,87 @@ static int fsl_spdif_probe(struct platform_device *pdev) return ret; } -#ifdef CONFIG_PM_SLEEP -static int fsl_spdif_suspend(struct device *dev) +#ifdef CONFIG_PM +static int fsl_spdif_runtime_resume(struct device *dev) { struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev); + int ret; + int i; - regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC, - &spdif_priv->regcache_srpc); + ret = clk_prepare_enable(spdif_priv->coreclk); + if (ret) { + dev_err(dev, "failed to enable core clock\n"); + return ret; + } - regcache_cache_only(spdif_priv->regmap, true); + if (!IS_ERR(spdif_priv->spbaclk)) { + ret = clk_prepare_enable(spdif_priv->spbaclk); + if (ret) { + dev_err(dev, "failed to enable spba clock\n"); + goto disable_core_clk; + } + } + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + ret = clk_prepare_enable(spdif_priv->txclk[i]); + if (ret) + goto disable_spba_clk; + } + + request_bus_freq(BUS_FREQ_HIGH); + + regcache_cache_only(spdif_priv->regmap, false); regcache_mark_dirty(spdif_priv->regmap); + regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC, + SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK, + spdif_priv->regcache_srpc); + + ret = regcache_sync(spdif_priv->regmap); + if (ret) + goto disable_tx_clk; + return 0; + +disable_tx_clk: +disable_spba_clk: + for (i--; i >= 0; i--) + clk_disable_unprepare(spdif_priv->txclk[i]); + if (!IS_ERR(spdif_priv->spbaclk)) + clk_disable_unprepare(spdif_priv->spbaclk); +disable_core_clk: + clk_disable_unprepare(spdif_priv->coreclk); + + return ret; } -static int fsl_spdif_resume(struct device *dev) +static int fsl_spdif_runtime_suspend(struct device *dev) { struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev); + int i; - regcache_cache_only(spdif_priv->regmap, false); + regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC, + &spdif_priv->regcache_srpc); + regcache_cache_only(spdif_priv->regmap, true); - regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC, - SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK, - spdif_priv->regcache_srpc); + release_bus_freq(BUS_FREQ_HIGH); - return regcache_sync(spdif_priv->regmap); + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) + clk_disable_unprepare(spdif_priv->txclk[i]); + + if (!IS_ERR(spdif_priv->spbaclk)) + clk_disable_unprepare(spdif_priv->spbaclk); + clk_disable_unprepare(spdif_priv->coreclk); + + return 0; } -#endif /* CONFIG_PM_SLEEP */ +#endif static const struct dev_pm_ops fsl_spdif_pm = { - SET_SYSTEM_SLEEP_PM_OPS(fsl_spdif_suspend, fsl_spdif_resume) -}; - -static const struct of_device_id fsl_spdif_dt_ids[] = { - { .compatible = "fsl,imx35-spdif", }, - { .compatible = "fsl,vf610-spdif", }, - {} + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(fsl_spdif_runtime_suspend, fsl_spdif_runtime_resume, + NULL) }; -MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); static struct platform_driver fsl_spdif_driver = { .driver = { diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h index e6c61e07bc1a..add21ceb5402 100644 --- a/sound/soc/fsl/fsl_spdif.h +++ b/sound/soc/fsl/fsl_spdif.h @@ -63,6 +63,7 @@ #define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET) #define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET) #define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_RAW_CAPTURE_MODE (1 << 14) #define SCR_LOW_POWER (1 << 13) #define SCR_SOFT_RESET (1 << 12) #define SCR_TXFIFO_CTRL_OFFSET 10 @@ -163,7 +164,9 @@ enum spdif_txrate { SPDIF_TXRATE_32000 = 0, SPDIF_TXRATE_44100, SPDIF_TXRATE_48000, + SPDIF_TXRATE_88200, SPDIF_TXRATE_96000, + SPDIF_TXRATE_176400, SPDIF_TXRATE_192000, }; #define SPDIF_TXRATE_MAX (SPDIF_TXRATE_192000 + 1) @@ -177,7 +180,9 @@ enum spdif_txrate { #define FSL_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | \ SNDRV_PCM_RATE_192000) #define FSL_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \ diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index ed18bc69e095..b2070627d9e3 100644..100755 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -40,6 +40,8 @@ #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/busfreq-imx.h> #include <sound/core.h> #include <sound/pcm.h> @@ -266,6 +268,7 @@ struct fsl_ssi { bool synchronous; bool use_dma; bool use_dual_fifo; + bool use_dyna_fifo; bool has_ipg_clk_name; unsigned int fifo_depth; unsigned int slot_width; @@ -644,7 +647,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, * task from fifo0, fifo1 would be neglected at the end of each * period. But SSI would still access fifo1 with an invalid data. */ - if (ssi->use_dual_fifo) + if (ssi->use_dual_fifo || ssi->use_dyna_fifo) snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2); @@ -700,6 +703,10 @@ static int fsl_ssi_set_bclk(struct snd_pcm_substream *substream, /* Generate bit clock based on the slot number and slot width */ freq = slots * slot_width * params_rate(hw_params); + /* The slot_width is not fixed to 32 for normal mode */ + if (params_channels(hw_params) == 1) + freq = 2 * params_width(hw_params) * params_rate(hw_params); + /* Don't apply it to any non-baudclk circumstance */ if (IS_ERR(ssi->baudclk)) return -EINVAL; @@ -803,6 +810,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, unsigned int sample_size = params_width(hw_params); u32 wl = SSI_SxCCR_WL(sample_size); int ret; + struct fsl_ssi_regvals *vals = ssi->regvals; if (fsl_ssi_is_i2s_master(ssi)) { ret = fsl_ssi_set_bclk(substream, dai, hw_params); @@ -852,6 +860,24 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, tx2 = tx || ssi->synchronous; regmap_update_bits(regs, REG_SSI_SxCCR(tx2), SSI_SxCCR_WL_MASK, wl); + if (ssi->use_dyna_fifo) { + if (channels == 1) { + ssi->dma_params_tx.fifo_num = 1; + ssi->dma_params_rx.fifo_num = 1; + vals[RX].srcr &= ~SSI_SRCR_RFEN1; + vals[TX].stcr &= ~SSI_STCR_TFEN1; + vals[RX].scr &= ~SSI_SCR_TCH_EN; + vals[TX].scr &= ~SSI_SCR_TCH_EN; + } else { + ssi->dma_params_tx.fifo_num = 2; + ssi->dma_params_rx.fifo_num = 2; + vals[RX].srcr |= SSI_SRCR_RFEN1; + vals[TX].stcr |= SSI_STCR_TFEN1; + vals[RX].scr |= SSI_SCR_TCH_EN; + vals[TX].scr |= SSI_SCR_TCH_EN; + } + } + return 0; } @@ -1331,6 +1357,8 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev, dev_dbg(dev, "failed to get baud clock: %ld\n", PTR_ERR(ssi->baudclk)); + ssi->dma_params_rx.chan_name = "rx"; + ssi->dma_params_tx.chan_name = "tx"; ssi->dma_params_tx.maxburst = ssi->dma_maxburst; ssi->dma_params_rx.maxburst = ssi->dma_maxburst; ssi->dma_params_tx.addr = ssi->ssi_phys + REG_SSI_STX0; @@ -1356,7 +1384,7 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev, if (ret) goto error_pcm; } else { - ret = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE); + ret = imx_pcm_platform_register(&pdev->dev); if (ret) goto error_pcm; } @@ -1437,6 +1465,8 @@ static int fsl_ssi_probe_from_dt(struct fsl_ssi *ssi) if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_SSI_DUAL) ssi->use_dual_fifo = true; + if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_MULTI_SAI) + ssi->use_dyna_fifo = true; /* * Backward compatible for older bindings by manually triggering the * machine driver's probe(). Use /compatible property, including the @@ -1517,8 +1547,10 @@ static int fsl_ssi_probe(struct platform_device *pdev) } ssi->irq = platform_get_irq(pdev, 0); - if (ssi->irq < 0) + if (ssi->irq < 0) { + dev_err(dev, "no irq for node %s\n", pdev->name); return ssi->irq; + } /* Set software limitations for synchronous mode except AC97 */ if (ssi->synchronous && !fsl_ssi_is_ac97(ssi)) { @@ -1555,6 +1587,7 @@ static int fsl_ssi_probe(struct platform_device *pdev) } dev_set_drvdata(dev, ssi); + pm_runtime_enable(&pdev->dev); if (ssi->soc->imx) { ret = fsl_ssi_imx_probe(pdev, ssi, iomem); @@ -1654,6 +1687,20 @@ static int fsl_ssi_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int fsl_ssi_runtime_resume(struct device *dev) +{ + request_bus_freq(BUS_FREQ_AUDIO); + return 0; +} + +static int fsl_ssi_runtime_suspend(struct device *dev) +{ + release_bus_freq(BUS_FREQ_AUDIO); + return 0; +} +#endif + #ifdef CONFIG_PM_SLEEP static int fsl_ssi_suspend(struct device *dev) { @@ -1688,6 +1735,8 @@ static int fsl_ssi_resume(struct device *dev) static const struct dev_pm_ops fsl_ssi_pm = { SET_SYSTEM_SLEEP_PM_OPS(fsl_ssi_suspend, fsl_ssi_resume) + SET_RUNTIME_PM_OPS(fsl_ssi_runtime_suspend, fsl_ssi_runtime_resume, + NULL) }; static struct platform_driver fsl_ssi_driver = { diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c new file mode 100644 index 000000000000..47a33ef139a8 --- /dev/null +++ b/sound/soc/fsl/fsl_xcvr.c @@ -0,0 +1,1384 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +#include <linux/bitrev.h> +#include <linux/clk.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <sound/pcm_params.h> + +#include "fsl_xcvr.h" +#include "imx-pcm.h" + +static const struct fsl_xcvr_pll_conf { + u8 mfi; /* min=0x18, max=0x38 */ + u32 mfn; /* signed int, 2's compl., min=0x3FFF0000, max=0x00010000 */ + u32 mfd; /* unsigned int */ + u32 fout; /* Fout = Fref*(MFI + MFN/MFD), Fref is 24MHz */ +} fsl_xcvr_pll_cfg[] = { + { .mfi = 54, .mfn = 1, .mfd = 6, .fout = 1300000000, }, /* 1.3 GHz */ + { .mfi = 32, .mfn = 96, .mfd = 125, .fout = 786432000, }, /* 8000 Hz */ + { .mfi = 30, .mfn = 66, .mfd = 625, .fout = 722534400, }, /* 11025 Hz */ + { .mfi = 29, .mfn = 1, .mfd = 6, .fout = 700000000, }, /* 700 MHz */ +}; + +/* + * HDMI2.1 spec defines 6- and 12-channels layout for one bit audio + * stream. Todo: to check how this case can be considered below + */ +static const u32 fsl_xcvr_earc_channels[] = { 1, 2, 8, 16, 32, }; +static const struct snd_pcm_hw_constraint_list fsl_xcvr_earc_channels_constr = { + .count = ARRAY_SIZE(fsl_xcvr_earc_channels), + .list = fsl_xcvr_earc_channels, +}; + +static const u32 fsl_xcvr_earc_rates[] = { + 32000, 44100, 48000, 64000, 88200, 96000, + 128000, 176400, 192000, 256000, 352800, 384000, + 512000, 705600, 768000, 1024000, 1411200, 1536000, +}; +static const struct snd_pcm_hw_constraint_list fsl_xcvr_earc_rates_constr = { + .count = ARRAY_SIZE(fsl_xcvr_earc_rates), + .list = fsl_xcvr_earc_rates, +}; + +static const u32 fsl_xcvr_spdif_channels[] = { 2, }; +static const struct snd_pcm_hw_constraint_list fsl_xcvr_spdif_channels_constr = { + .count = ARRAY_SIZE(fsl_xcvr_spdif_channels), + .list = fsl_xcvr_spdif_channels, +}; + +static const u32 fsl_xcvr_spdif_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, +}; +static const struct snd_pcm_hw_constraint_list fsl_xcvr_spdif_rates_constr = { + .count = ARRAY_SIZE(fsl_xcvr_spdif_rates), + .list = fsl_xcvr_spdif_rates, +}; + +static int fsl_xcvr_arc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + + xcvr->arc_mode = snd_soc_enum_item_to_val(e, item[0]); + + return 0; +} + +static int fsl_xcvr_arc_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + ucontrol->value.enumerated.item[0] = xcvr->arc_mode; + + return 0; +} + +static const u32 fsl_xcvr_phy_arc_cfg[] = { + FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN, FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN, +}; + +static const char * const fsl_xcvr_arc_mode[] = { "Single Ended", "Common", }; +static const struct soc_enum fsl_xcvr_arc_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fsl_xcvr_arc_mode), fsl_xcvr_arc_mode); +static struct snd_kcontrol_new fsl_xcvr_arc_mode_kctl = + SOC_ENUM_EXT("ARC Mode", fsl_xcvr_arc_mode_enum, + fsl_xcvr_arc_mode_get, fsl_xcvr_arc_mode_put); + +/* Capabilities data structure, bytes */ +static int fsl_xcvr_type_capds_bytes_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = FSL_XCVR_CAPDS_SIZE; + + return 0; +} + +static int fsl_xcvr_capds_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(ucontrol->value.bytes.data, xcvr->cap_ds, FSL_XCVR_CAPDS_SIZE); + + return 0; +} + +static int fsl_xcvr_capds_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(xcvr->cap_ds, ucontrol->value.bytes.data, FSL_XCVR_CAPDS_SIZE); + + return 0; +} + +static struct snd_kcontrol_new fsl_xcvr_earc_capds_kctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Capabilities Data Structure", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = fsl_xcvr_type_capds_bytes_info, + .get = fsl_xcvr_capds_get, + .put = fsl_xcvr_capds_put, +}; + +static int fsl_xcvr_activate_ctl(struct snd_soc_dai *dai, const char *name, + bool active) +{ + struct snd_soc_card *card = dai->component->card; + struct snd_kcontrol *kctl; + bool enabled; + + kctl = snd_soc_card_get_kcontrol(card, name); + if (kctl == NULL) + return -ENOENT; + + enabled = ((kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE) != 0); + if (active == enabled) + return 0; /* nothing to do */ + + if (active) + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_WRITE; + else + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE; + + snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id); + + return 1; +} + +static int fsl_xcvr_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct snd_soc_card *card = dai->component->card; + struct snd_soc_pcm_runtime *rtd; + + xcvr->mode = snd_soc_enum_item_to_val(e, item[0]); + + fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, + (xcvr->mode == FSL_XCVR_MODE_ARC)); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, + (xcvr->mode == FSL_XCVR_MODE_EARC)); + /* Allow playback for SPDIF only */ + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count = + (xcvr->mode == FSL_XCVR_MODE_SPDIF ? 1 : 0); + return 0; +} + +static int fsl_xcvr_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + ucontrol->value.enumerated.item[0] = xcvr->mode; + + return 0; +} + +static const char * const fsl_xcvr_mode[] = { "SPDIF", "ARC RX", "eARC", }; +static const struct soc_enum fsl_xcvr_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fsl_xcvr_mode), fsl_xcvr_mode); +static struct snd_kcontrol_new fsl_xcvr_mode_kctl = + SOC_ENUM_EXT("XCVR Mode", fsl_xcvr_mode_enum, + fsl_xcvr_mode_get, fsl_xcvr_mode_put); + +/** phy: true => phy, false => pll */ +static int fsl_xcvr_ai_write(struct fsl_xcvr *xcvr, u8 reg, u32 data, bool phy) +{ + struct device *dev = &xcvr->pdev->dev; + u32 val, idx, tidx; + int ret; + + idx = BIT(phy ? 26 : 24); + tidx = BIT(phy ? 27 : 25); + + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_CLR, 0xFF); + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, reg); + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_WDATA, data); + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_TOG, idx); + + ret = regmap_read_poll_timeout(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL, val, + (val & idx) == ((val & tidx) >> 1), + 10, 10000); + if (ret) + dev_err(dev, "AI timeout: failed to set %s reg 0x%02x=0x%08x\n", + phy ? "PHY" : "PLL", reg, data); + return ret; +} + +static int fsl_xcvr_en_phy_pll(struct fsl_xcvr *xcvr, u32 freq, bool tx) +{ + struct device *dev = &xcvr->pdev->dev; + u32 i, div = 0, log2; + int ret; + + for (i = 0; i < ARRAY_SIZE(fsl_xcvr_pll_cfg); i++) { + if (fsl_xcvr_pll_cfg[i].fout % freq == 0) { + div = fsl_xcvr_pll_cfg[i].fout / freq; + break; + } + } + + if (!div || i >= ARRAY_SIZE(fsl_xcvr_pll_cfg)) + return -EINVAL; + + log2 = ilog2(div); + + /* Release AI interface from reset */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, + FSL_XCVR_PHY_AI_CTRL_AI_RESETN); + if (ret < 0) { + dev_err(dev, "Error while setting IER0: %d\n", ret); + return ret; + } + + /* PLL: BANDGAP_SET: EN_VBG (enable bandgap) */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_BANDGAP_SET, + FSL_XCVR_PLL_BANDGAP_EN_VBG, 0); + + /* PLL: CTRL0: DIV_INTEGER */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0, fsl_xcvr_pll_cfg[i].mfi, 0); + /* PLL: NUMERATOR: MFN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_NUM, fsl_xcvr_pll_cfg[i].mfn, 0); + /* PLL: DENOMINATOR: MFD */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_DEN, fsl_xcvr_pll_cfg[i].mfd, 0); + /* PLL: CTRL0_SET: HOLD_RING_OFF, POWER_UP */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET, + FSL_XCVR_PLL_CTRL0_HROFF | FSL_XCVR_PLL_CTRL0_PWP, 0); + udelay(25); + /* PLL: CTRL0: Clear Hold Ring Off */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_CLR, + FSL_XCVR_PLL_CTRL0_HROFF, 0); + udelay(100); + if (tx) { /* TX is enabled for SPDIF only */ + /* PLL: POSTDIV: PDIV0 */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV, + FSL_XCVR_PLL_PDIVx(log2, 0), 0); + /* PLL: CTRL_SET: CLKMUX0_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET, + FSL_XCVR_PLL_CTRL0_CM0_EN, 0); + } else if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC RX */ + /* PLL: POSTDIV: PDIV1 */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV, + FSL_XCVR_PLL_PDIVx(log2, 1), 0); + /* PLL: CTRL_SET: CLKMUX1_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET, + FSL_XCVR_PLL_CTRL0_CM1_EN, 0); + } else { /* SPDIF / ARC RX */ + /* PLL: POSTDIV: PDIV2 */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV, + FSL_XCVR_PLL_PDIVx(log2, 2), 0); + /* PLL: CTRL_SET: CLKMUX2_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET, + FSL_XCVR_PLL_CTRL0_CM2_EN, 0); + } + + if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC mode */ + /* PHY: CTRL_SET: TX_DIFF_OE, PHY_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_TSDIFF_OE | + FSL_XCVR_PHY_CTRL_PHY_EN, 1); + /* PHY: CTRL2_SET: EARC_TX_MODE */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL2_SET, + FSL_XCVR_PHY_CTRL2_EARC_TXMS, 1); + } else if (!tx) { /* SPDIF / ARC RX mode */ + if (xcvr->mode == FSL_XCVR_MODE_SPDIF) + /* PHY: CTRL_SET: SPDIF_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_SPDIF_EN, 1); + else /* PHY: CTRL_SET: ARC RX setup */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_PHY_EN | + FSL_XCVR_PHY_CTRL_RX_CM_EN | + fsl_xcvr_phy_arc_cfg[xcvr->arc_mode], 1); + } + + dev_dbg(dev, "PLL Fexp: %u, Fout: %u, mfi: %u, mfn: %u, mfd: %d, div: %u, pdiv0: %u\n", + freq, fsl_xcvr_pll_cfg[i].fout, fsl_xcvr_pll_cfg[i].mfi, + fsl_xcvr_pll_cfg[i].mfn, fsl_xcvr_pll_cfg[i].mfd, div, log2); + return 0; +} + +static int fsl_xcvr_en_aud_pll(struct fsl_xcvr *xcvr, u32 freq) +{ + struct device *dev = &xcvr->pdev->dev; + int ret; + + clk_disable_unprepare(xcvr->phy_clk); + ret = clk_set_rate(xcvr->phy_clk, freq); + if (ret < 0) { + dev_err(dev, "Error while setting AUD PLL rate: %d\n", ret); + return ret; + } + ret = clk_prepare_enable(xcvr->phy_clk); + if (ret) { + dev_err(dev, "failed to start PHY clock: %d\n", ret); + return ret; + } + + /* Release AI interface from reset */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, + FSL_XCVR_PHY_AI_CTRL_AI_RESETN); + if (ret < 0) { + dev_err(dev, "Error while setting IER0: %d\n", ret); + return ret; + } + + if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC mode */ + /* PHY: CTRL_SET: TX_DIFF_OE, PHY_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_TSDIFF_OE | + FSL_XCVR_PHY_CTRL_PHY_EN, 1); + /* PHY: CTRL2_SET: EARC_TX_MODE */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL2_SET, + FSL_XCVR_PHY_CTRL2_EARC_TXMS, 1); + } else { /* SPDIF mode */ + /* PHY: CTRL_SET: TX_CLK_AUD_SS | SPDIF_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_TX_CLK_AUD_SS | + FSL_XCVR_PHY_CTRL_SPDIF_EN, 1); + } + + dev_dbg(dev, "PLL Fexp: %u\n", freq); + + return 0; +} + +#define FSL_XCVR_SPDIF_RX_FREQ 175000000 +static int fsl_xcvr_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 m_ctl = 0, v_ctl = 0; + u32 r = substream->runtime->rate, ch = substream->runtime->channels; + u32 fout = 32 * r * ch * 10 * 2; + int ret = 0; + + switch (xcvr->mode) { + case FSL_XCVR_MODE_SPDIF: + case FSL_XCVR_MODE_ARC: + if (tx) { + ret = fsl_xcvr_en_aud_pll(xcvr, fout); + if (ret < 0) { + dev_err(dai->dev, "Failed to set TX freq %u: %d\n", + fout, ret); + return ret; + } + + ret = regmap_write(xcvr->regmap, FSL_XCVR_TX_DPTH_CTRL_SET, + FSL_XCVR_TX_DPTH_CTRL_FRM_FMT); + if (ret < 0) { + dev_err(dai->dev, "Failed to set TX_DPTH: %d\n", ret); + return ret; + } + + /** + * set SPDIF MODE - this flag is used to gate + * SPDIF output, useless for SPDIF RX + */ + m_ctl |= FSL_XCVR_EXT_CTRL_SPDIF_MODE; + v_ctl |= FSL_XCVR_EXT_CTRL_SPDIF_MODE; + } else { + /** + * Clear RX FIFO, flip RX FIFO bits, + * disable eARC related HW mode detects + */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_SET, + FSL_XCVR_RX_DPTH_CTRL_STORE_FMT | + FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO | + FSL_XCVR_RX_DPTH_CTRL_COMP | + FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL); + if (ret < 0) { + dev_err(dai->dev, "Failed to set RX_DPTH: %d\n", ret); + return ret; + } + + ret = fsl_xcvr_en_phy_pll(xcvr, FSL_XCVR_SPDIF_RX_FREQ, tx); + if (ret < 0) { + dev_err(dai->dev, "Failed to set RX freq %u: %d\n", + FSL_XCVR_SPDIF_RX_FREQ, ret); + return ret; + } + } + break; + case FSL_XCVR_MODE_EARC: + if (!tx) { + /** Clear RX FIFO, flip RX FIFO bits */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_SET, + FSL_XCVR_RX_DPTH_CTRL_STORE_FMT | + FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO); + if (ret < 0) { + dev_err(dai->dev, "Failed to set RX_DPTH: %d\n", ret); + return ret; + } + + /** Enable eARC related HW mode detects */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_CLR, + FSL_XCVR_RX_DPTH_CTRL_COMP | + FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL); + if (ret < 0) { + dev_err(dai->dev, "Failed to clr TX_DPTH: %d\n", ret); + return ret; + } + } + + /* clear CMDC RESET */ + m_ctl |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx); + /* set TX_RX_MODE */ + m_ctl |= FSL_XCVR_EXT_CTRL_TX_RX_MODE; + v_ctl |= (tx ? FSL_XCVR_EXT_CTRL_TX_RX_MODE : 0); + break; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL); + if (ret < 0) { + dev_err(dai->dev, "Error while setting IER0: %d\n", ret); + return ret; + } + + /* clear DPATH RESET */ + m_ctl |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx); + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, m_ctl, v_ctl); + if (ret < 0) { + dev_err(dai->dev, "Error while setting EXT_CTRL: %d\n", ret); + return ret; + } + + return 0; +} + +static int fsl_xcvr_constr(const struct snd_pcm_substream *substream, + const struct snd_pcm_hw_constraint_list *channels, + const struct snd_pcm_hw_constraint_list *rates) +{ + struct snd_pcm_runtime *rt = substream->runtime; + int ret; + + ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + channels); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, + rates); + if (ret < 0) + return ret; + + return 0; +} + +static int fsl_xcvr_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0; + + if (xcvr->streams & BIT(substream->stream)) { + dev_err(dai->dev, "%sX busy\n", tx ? "T" : "R"); + return -EBUSY; + } + + switch (xcvr->mode) { + case FSL_XCVR_MODE_SPDIF: + case FSL_XCVR_MODE_ARC: + ret = fsl_xcvr_constr(substream, &fsl_xcvr_spdif_channels_constr, + &fsl_xcvr_spdif_rates_constr); + break; + case FSL_XCVR_MODE_EARC: + ret = fsl_xcvr_constr(substream, &fsl_xcvr_earc_channels_constr, + &fsl_xcvr_earc_rates_constr); + break; + } + if (ret < 0) + return ret; + + xcvr->streams |= BIT(substream->stream); + + /* Disable XCVR controls if there is stream started */ + fsl_xcvr_activate_ctl(dai, fsl_xcvr_mode_kctl.name, false); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, false); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, false); + + return 0; +} + +static void fsl_xcvr_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 mask = 0, val = 0; + int ret; + + xcvr->streams &= ~BIT(substream->stream); + + /* Enable XCVR controls if there is no stream started */ + if (!xcvr->streams) { + fsl_xcvr_activate_ctl(dai, fsl_xcvr_mode_kctl.name, true); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, + (xcvr->mode == FSL_XCVR_MODE_ARC)); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, + (xcvr->mode == FSL_XCVR_MODE_EARC)); + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to set IER0: %d\n", ret); + return; + } + + /* clear SPDIF MODE */ + if (xcvr->mode == FSL_XCVR_MODE_SPDIF) + mask |= FSL_XCVR_EXT_CTRL_SPDIF_MODE; + } + + if (xcvr->mode == FSL_XCVR_MODE_EARC) { + /* set CMDC RESET */ + mask |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx); + val |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx); + } + + /* set DPATH RESET */ + mask |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx); + val |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx); + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, mask, val); + if (ret < 0) { + dev_err(dai->dev, "Err setting DPATH RESET: %d\n", ret); + return; + } +} + +static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (tx) { + switch (xcvr->mode) { + case FSL_XCVR_MODE_EARC: + /* set isr_cmdc_tx_en, w1c */ + ret = regmap_write(xcvr->regmap, + FSL_XCVR_ISR_SET, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, "err updating isr %d\n", ret); + return ret; + } + fallthrough; + case FSL_XCVR_MODE_SPDIF: + ret = regmap_write(xcvr->regmap, + FSL_XCVR_TX_DPTH_CTRL_SET, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret); + return ret; + } + break; + } + } + + /* enable DMA RD/WR */ + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable DMA: %d\n", ret); + return ret; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* disable DMA RD/WR */ + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), + FSL_XCVR_EXT_CTRL_DMA_DIS(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to disable DMA: %d\n", ret); + return ret; + } + + if (tx) { + switch (xcvr->mode) { + case FSL_XCVR_MODE_SPDIF: + ret = regmap_write(xcvr->regmap, + FSL_XCVR_TX_DPTH_CTRL_CLR, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret); + return ret; + } + fallthrough; + case FSL_XCVR_MODE_EARC: + /* clear ISR_CMDC_TX_EN, W1C */ + ret = regmap_write(xcvr->regmap, + FSL_XCVR_ISR_CLR, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, + "Err updating ISR %d\n", ret); + return ret; + } + break; + } + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) +{ + struct device *dev = &xcvr->pdev->dev; + const struct firmware *fw; + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET; + u32 mask, val; + + ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev); + if (ret) { + dev_err(dev, "failed to request firmware.\n"); + return ret; + } + + rem = fw->size; + + /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */ + if (rem > 16384) { + dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem); + return -ENOMEM; + } + + for (page = 0; page < 10; page++) { + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_PAGE_MASK, + FSL_XCVR_EXT_CTRL_PAGE(page)); + if (ret < 0) { + dev_err(dev, "FW: failed to set page %d, err=%d\n", + page, ret); + goto err_firmware; + } + + off = page * size; + out = min(rem, size); + /* IPG clock is assumed to be running, otherwise it will hang */ + if (out > 0) { + /* write firmware into code memory */ + memcpy_toio(xcvr->ram_addr, fw->data + off, out); + rem -= out; + if (rem == 0) { + /* last part of firmware written */ + /* clean remaining part of code memory page */ + memset_io(xcvr->ram_addr + out, 0, size - out); + } + } else { + /* clean current page, including data memory */ + memset_io(xcvr->ram_addr, 0, size); + } + }; + +err_firmware: + release_firmware(fw); + if (ret < 0) + return ret; + + /* configure watermarks */ + mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK; + val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX); + val |= FSL_XCVR_EXT_CTRL_TX_FWM(FSL_XCVR_FIFO_WMK_TX); + /* disable DMA RD/WR */ + mask |= FSL_XCVR_EXT_CTRL_DMA_RD_DIS | FSL_XCVR_EXT_CTRL_DMA_WR_DIS; + val |= FSL_XCVR_EXT_CTRL_DMA_RD_DIS | FSL_XCVR_EXT_CTRL_DMA_WR_DIS; + /* Data RAM is 4KiB, last two pages: 8 and 9. Select page 8. */ + mask |= FSL_XCVR_EXT_CTRL_PAGE_MASK; + val |= FSL_XCVR_EXT_CTRL_PAGE(8); + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, mask, val); + if (ret < 0) { + dev_err(dev, "Failed to set watermarks: %d\n", ret); + return ret; + } + + /* Store Capabilities Data Structure into Data RAM */ + memcpy_toio(xcvr->ram_addr + FSL_XCVR_CAP_DATA_STR, xcvr->cap_ds, + FSL_XCVR_CAPDS_SIZE); + return 0; +} + +static int fsl_xcvr_type_iec958_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int fsl_xcvr_type_iec958_bytes_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = FIELD_SIZEOF(struct snd_aes_iec958, status); + + return 0; +} + +static int fsl_xcvr_rx_cs_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(ucontrol->value.iec958.status, xcvr->rx_iec958.status, 24); + + return 0; +} + +static int fsl_xcvr_tx_cs_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(ucontrol->value.iec958.status, xcvr->tx_iec958.status, 24); + + return 0; +} + +static int fsl_xcvr_tx_cs_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(xcvr->tx_iec958.status, ucontrol->value.iec958.status, 24); + + return 0; +} + +static struct snd_kcontrol_new fsl_xcvr_rx_ctls[] = { + /* Channel status controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = fsl_xcvr_type_iec958_info, + .get = fsl_xcvr_rx_cs_get, + }, + /* Capture channel status, bytes */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Capture Channel Status", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = fsl_xcvr_type_iec958_bytes_info, + .get = fsl_xcvr_rx_cs_get, + }, +}; + +static struct snd_kcontrol_new fsl_xcvr_tx_ctls[] = { + /* Channel status controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = fsl_xcvr_type_iec958_info, + .get = fsl_xcvr_tx_cs_get, + .put = fsl_xcvr_tx_cs_put, + }, + /* Playback channel status, bytes */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Playback Channel Status", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = fsl_xcvr_type_iec958_bytes_info, + .get = fsl_xcvr_tx_cs_get, + .put = fsl_xcvr_tx_cs_put, + }, +}; + +static struct snd_soc_dai_ops fsl_xcvr_dai_ops = { + .prepare = fsl_xcvr_prepare, + .startup = fsl_xcvr_startup, + .shutdown = fsl_xcvr_shutdown, + .trigger = fsl_xcvr_trigger, +}; + +static int fsl_xcvr_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &xcvr->dma_prms_tx, &xcvr->dma_prms_rx); + snd_soc_dai_set_drvdata(dai, xcvr); + + snd_soc_add_dai_controls(dai, &fsl_xcvr_mode_kctl, 1); + snd_soc_add_dai_controls(dai, &fsl_xcvr_arc_mode_kctl, 1); + snd_soc_add_dai_controls(dai, &fsl_xcvr_earc_capds_kctl, 1); + snd_soc_add_dai_controls(dai, fsl_xcvr_tx_ctls, + ARRAY_SIZE(fsl_xcvr_tx_ctls)); + snd_soc_add_dai_controls(dai, fsl_xcvr_rx_ctls, + ARRAY_SIZE(fsl_xcvr_rx_ctls)); + return 0; +} + +static struct snd_soc_dai_driver fsl_xcvr_dai = { + .probe = fsl_xcvr_dai_probe, + .ops = &fsl_xcvr_dai_ops, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 32000, + .rate_max = 1536000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 32000, + .rate_max = 1536000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, +}; + +static const struct snd_soc_component_driver fsl_xcvr_comp = { + .name = "fsl-xcvr-dai", +}; + +static const struct reg_default fsl_xcvr_reg_defaults[] = { + { FSL_XCVR_VERSION, 0x00000000 }, + { FSL_XCVR_EXT_CTRL, 0xF8204040 }, + { FSL_XCVR_EXT_STATUS, 0x00000000 }, + { FSL_XCVR_EXT_IER0, 0x00000000 }, + { FSL_XCVR_EXT_IER1, 0x00000000 }, + { FSL_XCVR_EXT_ISR, 0x00000000 }, + { FSL_XCVR_EXT_ISR_SET, 0x00000000 }, + { FSL_XCVR_EXT_ISR_CLR, 0x00000000 }, + { FSL_XCVR_EXT_ISR_TOG, 0x00000000 }, + { FSL_XCVR_IER, 0x00000000 }, + { FSL_XCVR_ISR, 0x00000000 }, + { FSL_XCVR_ISR_SET, 0x00000000 }, + { FSL_XCVR_ISR_CLR, 0x00000000 }, + { FSL_XCVR_ISR_TOG, 0x00000000 }, + { FSL_XCVR_RX_DPTH_CTRL, 0x00002C89 }, + { FSL_XCVR_RX_DPTH_CTRL_SET, 0x00002C89 }, + { FSL_XCVR_RX_DPTH_CTRL_CLR, 0x00002C89 }, + { FSL_XCVR_RX_DPTH_CTRL_TOG, 0x00002C89 }, + { FSL_XCVR_RX_DPTH_CNTR_CTRL, 0x00000000 }, + { FSL_XCVR_RX_DPTH_CNTR_CTRL_SET, 0x00000000 }, + { FSL_XCVR_RX_DPTH_CNTR_CTRL_CLR, 0x00000000 }, + { FSL_XCVR_RX_DPTH_CNTR_CTRL_TOG, 0x00000000 }, + { FSL_XCVR_RX_DPTH_TSCR, 0x00000000 }, + { FSL_XCVR_RX_DPTH_BCR, 0x00000000 }, + { FSL_XCVR_RX_DPTH_BCTR, 0x00000000 }, + { FSL_XCVR_RX_DPTH_BCRR, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CTRL, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CTRL_SET, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CTRL_CLR, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CTRL_TOG, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_0, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_1, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_2, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_3, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_4, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_5, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CNTR_CTRL, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CNTR_CTRL_SET, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CNTR_CTRL_CLR, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CNTR_CTRL_TOG, 0x00000000 }, + { FSL_XCVR_TX_DPTH_TSCR, 0x00000000 }, + { FSL_XCVR_TX_DPTH_BCR, 0x00000000 }, + { FSL_XCVR_TX_DPTH_BCTR, 0x00000000 }, + { FSL_XCVR_TX_DPTH_BCRR, 0x00000000 }, + { FSL_XCVR_DEBUG_REG_0, 0x00000000 }, + { FSL_XCVR_DEBUG_REG_1, 0x00000000 }, +}; + +static bool fsl_xcvr_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_XCVR_VERSION: + case FSL_XCVR_EXT_CTRL: + case FSL_XCVR_EXT_STATUS: + case FSL_XCVR_EXT_IER0: + case FSL_XCVR_EXT_IER1: + case FSL_XCVR_EXT_ISR: + case FSL_XCVR_EXT_ISR_SET: + case FSL_XCVR_EXT_ISR_CLR: + case FSL_XCVR_EXT_ISR_TOG: + case FSL_XCVR_IER: + case FSL_XCVR_ISR: + case FSL_XCVR_ISR_SET: + case FSL_XCVR_ISR_CLR: + case FSL_XCVR_ISR_TOG: + case FSL_XCVR_PHY_AI_CTRL: + case FSL_XCVR_PHY_AI_CTRL_SET: + case FSL_XCVR_PHY_AI_CTRL_CLR: + case FSL_XCVR_PHY_AI_CTRL_TOG: + case FSL_XCVR_PHY_AI_RDATA: + case FSL_XCVR_CLK_CTRL: + case FSL_XCVR_RX_DPTH_CTRL: + case FSL_XCVR_RX_DPTH_CTRL_SET: + case FSL_XCVR_RX_DPTH_CTRL_CLR: + case FSL_XCVR_RX_DPTH_CTRL_TOG: + case FSL_XCVR_RX_DPTH_CNTR_CTRL: + case FSL_XCVR_RX_DPTH_CNTR_CTRL_SET: + case FSL_XCVR_RX_DPTH_CNTR_CTRL_CLR: + case FSL_XCVR_RX_DPTH_CNTR_CTRL_TOG: + case FSL_XCVR_RX_DPTH_TSCR: + case FSL_XCVR_RX_DPTH_BCR: + case FSL_XCVR_RX_DPTH_BCTR: + case FSL_XCVR_RX_DPTH_BCRR: + case FSL_XCVR_TX_DPTH_CTRL: + case FSL_XCVR_TX_DPTH_CTRL_SET: + case FSL_XCVR_TX_DPTH_CTRL_CLR: + case FSL_XCVR_TX_DPTH_CTRL_TOG: + case FSL_XCVR_TX_CS_DATA_0: + case FSL_XCVR_TX_CS_DATA_1: + case FSL_XCVR_TX_CS_DATA_2: + case FSL_XCVR_TX_CS_DATA_3: + case FSL_XCVR_TX_CS_DATA_4: + case FSL_XCVR_TX_CS_DATA_5: + case FSL_XCVR_TX_DPTH_CNTR_CTRL: + case FSL_XCVR_TX_DPTH_CNTR_CTRL_SET: + case FSL_XCVR_TX_DPTH_CNTR_CTRL_CLR: + case FSL_XCVR_TX_DPTH_CNTR_CTRL_TOG: + case FSL_XCVR_TX_DPTH_TSCR: + case FSL_XCVR_TX_DPTH_BCR: + case FSL_XCVR_TX_DPTH_BCTR: + case FSL_XCVR_TX_DPTH_BCRR: + case FSL_XCVR_DEBUG_REG_0: + case FSL_XCVR_DEBUG_REG_1: + return true; + default: + return false; + } +} + +static bool fsl_xcvr_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_XCVR_EXT_CTRL: + case FSL_XCVR_EXT_IER0: + case FSL_XCVR_EXT_IER1: + case FSL_XCVR_EXT_ISR: + case FSL_XCVR_EXT_ISR_SET: + case FSL_XCVR_EXT_ISR_CLR: + case FSL_XCVR_EXT_ISR_TOG: + case FSL_XCVR_IER: + case FSL_XCVR_ISR_SET: + case FSL_XCVR_ISR_CLR: + case FSL_XCVR_ISR_TOG: + case FSL_XCVR_PHY_AI_CTRL: + case FSL_XCVR_PHY_AI_CTRL_SET: + case FSL_XCVR_PHY_AI_CTRL_CLR: + case FSL_XCVR_PHY_AI_CTRL_TOG: + case FSL_XCVR_PHY_AI_WDATA: + case FSL_XCVR_CLK_CTRL: + case FSL_XCVR_RX_DPTH_CTRL: + case FSL_XCVR_RX_DPTH_CTRL_SET: + case FSL_XCVR_RX_DPTH_CTRL_CLR: + case FSL_XCVR_RX_DPTH_CTRL_TOG: + case FSL_XCVR_RX_DPTH_CNTR_CTRL: + case FSL_XCVR_RX_DPTH_CNTR_CTRL_SET: + case FSL_XCVR_RX_DPTH_CNTR_CTRL_CLR: + case FSL_XCVR_RX_DPTH_CNTR_CTRL_TOG: + case FSL_XCVR_TX_DPTH_CTRL_SET: + case FSL_XCVR_TX_DPTH_CTRL_CLR: + case FSL_XCVR_TX_DPTH_CTRL_TOG: + case FSL_XCVR_TX_CS_DATA_0: + case FSL_XCVR_TX_CS_DATA_1: + case FSL_XCVR_TX_CS_DATA_2: + case FSL_XCVR_TX_CS_DATA_3: + case FSL_XCVR_TX_CS_DATA_4: + case FSL_XCVR_TX_CS_DATA_5: + case FSL_XCVR_TX_DPTH_CNTR_CTRL: + case FSL_XCVR_TX_DPTH_CNTR_CTRL_SET: + case FSL_XCVR_TX_DPTH_CNTR_CTRL_CLR: + case FSL_XCVR_TX_DPTH_CNTR_CTRL_TOG: + return true; + default: + return false; + } +} + +static bool fsl_xcvr_volatile_reg(struct device *dev, unsigned int reg) +{ + return fsl_xcvr_readable_reg(dev, reg); +} + +static const struct regmap_config fsl_xcvr_regmap_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = FSL_XCVR_MAX_REG, + .reg_defaults = fsl_xcvr_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_xcvr_reg_defaults), + .readable_reg = fsl_xcvr_readable_reg, + .volatile_reg = fsl_xcvr_volatile_reg, + .writeable_reg = fsl_xcvr_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static irqreturn_t irq0_isr(int irq, void *devid) +{ + struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid; + struct device *dev = &xcvr->pdev->dev; + struct regmap *regmap = xcvr->regmap; + void __iomem *reg_ctrl, *reg_buff; + u32 isr, isr_clr = 0, val, i; + + regmap_read(regmap, FSL_XCVR_EXT_ISR, &isr); + + if (isr & FSL_XCVR_IRQ_NEW_CS) { + dev_dbg(dev, "Received new CS block\n"); + isr_clr |= FSL_XCVR_IRQ_NEW_CS; + /* Data RAM is 4KiB, last two pages: 8 and 9. Select page 8. */ + regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_PAGE_MASK, + FSL_XCVR_EXT_CTRL_PAGE(8)); + + /* Find updated CS buffer */ + reg_ctrl = xcvr->ram_addr + FSL_XCVR_RX_CS_CTRL_0; + reg_buff = xcvr->ram_addr + FSL_XCVR_RX_CS_BUFF_0; + memcpy_fromio(&val, reg_ctrl, sizeof(val)); + if (!val) { + reg_ctrl = xcvr->ram_addr + FSL_XCVR_RX_CS_CTRL_1; + reg_buff = xcvr->ram_addr + FSL_XCVR_RX_CS_BUFF_1; + memcpy_fromio(&val, reg_ctrl, sizeof(val)); + } + + if (val) { + /* copy CS buffer */ + memcpy_fromio(&xcvr->rx_iec958.status, reg_buff, + sizeof(xcvr->rx_iec958.status)); + for (i = 0; i < 6; i++) { + val = *(u32 *)(xcvr->rx_iec958.status + i*4); + *(u32 *)(xcvr->rx_iec958.status + i*4) = + bitrev32(val); + } + /* clear CS control register */ + memset_io(reg_ctrl, 0, sizeof(val)); + } + } + if (isr & FSL_XCVR_IRQ_NEW_UD) { + dev_dbg(dev, "Received new UD block\n"); + isr_clr |= FSL_XCVR_IRQ_NEW_UD; + } + if (isr & FSL_XCVR_IRQ_MUTE) { + dev_dbg(dev, "HW mute bit detected\n"); + isr_clr |= FSL_XCVR_IRQ_MUTE; + } + if (isr & FSL_XCVR_IRQ_FIFO_UOFL_ERR) { + dev_dbg(dev, "RX/TX FIFO full/empty\n"); + isr_clr |= FSL_XCVR_IRQ_FIFO_UOFL_ERR; + } + if (isr & FSL_XCVR_IRQ_ARC_MODE) { + dev_dbg(dev, "CMDC SM falls out of eARC mode\n"); + isr_clr |= FSL_XCVR_IRQ_ARC_MODE; + } + if (isr & FSL_XCVR_IRQ_DMA_RD_REQ) { + dev_dbg(dev, "DMA read request\n"); + isr_clr |= FSL_XCVR_IRQ_DMA_RD_REQ; + } + if (isr & FSL_XCVR_IRQ_DMA_WR_REQ) { + dev_dbg(dev, "DMA write request\n"); + isr_clr |= FSL_XCVR_IRQ_DMA_WR_REQ; + } + + if (isr_clr) { + regmap_write(regmap, FSL_XCVR_EXT_ISR_CLR, isr_clr); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static const struct fsl_xcvr_soc_data fsl_xcvr_imx8mp_data = { + .fw_name = "imx/xcvr/xcvr-imx8mp.bin", +}; + +static const struct of_device_id fsl_xcvr_dt_ids[] = { + { .compatible = "fsl,imx8mp-xcvr", .data = &fsl_xcvr_imx8mp_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_xcvr_dt_ids); + +static int fsl_xcvr_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct fsl_xcvr *xcvr; + struct resource *ram_res, *regs_res, *rx_res, *tx_res; + void __iomem *regs; + int ret, irq; + + of_id = of_match_device(fsl_xcvr_dt_ids, dev); + if (!of_id) + return -EINVAL; + + xcvr = devm_kzalloc(dev, sizeof(*xcvr), GFP_KERNEL); + if (!xcvr) + return -ENOMEM; + + xcvr->pdev = pdev; + xcvr->soc_data = of_device_get_match_data(&pdev->dev); + + xcvr->ipg_clk = devm_clk_get(dev, "ipg"); + if (IS_ERR(xcvr->ipg_clk)) { + dev_err(dev, "failed to get ipg clock\n"); + return PTR_ERR(xcvr->ipg_clk); + } + + xcvr->phy_clk = devm_clk_get(dev, "phy"); + if (IS_ERR(xcvr->phy_clk)) { + dev_err(dev, "failed to get phy clock\n"); + return PTR_ERR(xcvr->phy_clk); + } + + xcvr->spba_clk = devm_clk_get(dev, "spba"); + if (IS_ERR(xcvr->spba_clk)) { + dev_err(dev, "failed to get spba clock\n"); + return PTR_ERR(xcvr->spba_clk); + } + + xcvr->pll_ipg_clk = devm_clk_get(dev, "pll_ipg"); + if (IS_ERR(xcvr->pll_ipg_clk)) { + dev_err(dev, "failed to get pll_ipg clock\n"); + return PTR_ERR(xcvr->pll_ipg_clk); + } + + ram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ram"); + xcvr->ram_addr = devm_ioremap_resource(dev, ram_res); + if (IS_ERR(xcvr->ram_addr)) + return PTR_ERR(xcvr->ram_addr); + + regs_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + regs = devm_ioremap_resource(dev, regs_res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + xcvr->regmap = devm_regmap_init_mmio_clk(dev, NULL, regs, + &fsl_xcvr_regmap_cfg); + if (IS_ERR(xcvr->regmap)) { + dev_err(dev, "failed to init XCVR regmap: %ld\n", + PTR_ERR(xcvr->regmap)); + return PTR_ERR(xcvr->regmap); + } + + xcvr->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(xcvr->reset)) { + dev_err(dev, "failed to get XCVR reset control\n"); + return PTR_ERR(xcvr->reset); + } + + /* get IRQs */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "no irq[0]: %d\n", irq); + return irq; + } + + ret = devm_request_irq(dev, irq, irq0_isr, 0, pdev->name, xcvr); + if (ret) { + dev_err(dev, "failed to claim IRQ0: %i\n", ret); + return ret; + } + + rx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rxfifo"); + tx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "txfifo"); + xcvr->dma_prms_rx.chan_name = "rx"; + xcvr->dma_prms_tx.chan_name = "tx"; + xcvr->dma_prms_rx.addr = rx_res->start; + xcvr->dma_prms_tx.addr = tx_res->start; + xcvr->dma_prms_rx.maxburst = FSL_XCVR_MAXBURST_RX; + xcvr->dma_prms_tx.maxburst = FSL_XCVR_MAXBURST_TX; + + platform_set_drvdata(pdev, xcvr); + pm_runtime_enable(dev); + regcache_cache_only(xcvr->regmap, true); + + ret = devm_snd_soc_register_component(dev, &fsl_xcvr_comp, + &fsl_xcvr_dai, 1); + if (ret) { + dev_err(dev, "failed to register component %s\n", + fsl_xcvr_comp.name); + return ret; + } + + ret = imx_pcm_platform_register(dev); + if (ret) { + dev_err(dev, "failed to pcm register\n"); + return ret; + } + + ret = sysfs_create_group(&pdev->dev.kobj, fsl_xcvr_get_attr_grp()); + if (ret) + dev_err(&pdev->dev, "fail to create sys group\n"); + + return ret; +} + +static int fsl_xcvr_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, fsl_xcvr_get_attr_grp()); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static __maybe_unused int fsl_xcvr_runtime_suspend(struct device *dev) +{ + struct fsl_xcvr *xcvr = dev_get_drvdata(dev); + int ret; + + /* Assert M0+ reset */ + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_CORE_RESET, + FSL_XCVR_EXT_CTRL_CORE_RESET); + if (ret < 0) + dev_err(dev, "Failed to assert M0+ core: %d\n", ret); + + ret = reset_control_assert(xcvr->reset); + if (ret < 0) + dev_err(dev, "Failed to assert M0+ reset: %d\n", ret); + + regcache_cache_only(xcvr->regmap, true); + + clk_disable_unprepare(xcvr->spba_clk); + clk_disable_unprepare(xcvr->phy_clk); + clk_disable_unprepare(xcvr->pll_ipg_clk); + clk_disable_unprepare(xcvr->ipg_clk); + + return 0; +} + +static __maybe_unused int fsl_xcvr_runtime_resume(struct device *dev) +{ + struct fsl_xcvr *xcvr = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(xcvr->ipg_clk); + if (ret) { + dev_err(dev, "failed to start IPG clock.\n"); + return ret; + } + + ret = clk_prepare_enable(xcvr->pll_ipg_clk); + if (ret) { + dev_err(dev, "failed to start PLL IPG clock.\n"); + goto stop_ipg_clk; + } + + ret = clk_prepare_enable(xcvr->phy_clk); + if (ret) { + dev_err(dev, "failed to start PHY clock: %d\n", ret); + goto stop_pll_ipg_clk; + } + + ret = clk_prepare_enable(xcvr->spba_clk); + if (ret) { + dev_err(dev, "failed to start SPBA clock.\n"); + goto stop_phy_clk; + } + + regcache_cache_only(xcvr->regmap, false); + regcache_mark_dirty(xcvr->regmap); + ret = regcache_sync(xcvr->regmap); + + if (ret) { + dev_err(dev, "failed to sync regcache.\n"); + goto stop_spba_clk; + } + + ret = reset_control_deassert(xcvr->reset); + if (ret) { + dev_err(dev, "failed to deassert M0+ reset.\n"); + goto stop_spba_clk; + } + + ret = fsl_xcvr_load_firmware(xcvr); + if (ret) { + dev_err(dev, "failed to load firmware.\n"); + goto stop_spba_clk; + } + + /* Release M0+ reset */ + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_CORE_RESET, 0); + if (ret < 0) { + dev_err(dev, "M0+ core release failed: %d\n", ret); + goto stop_spba_clk; + } + + /* Let M0+ core complete firmware initialization */ + msleep(50); + + return 0; + +stop_spba_clk: + clk_disable_unprepare(xcvr->spba_clk); +stop_phy_clk: + clk_disable_unprepare(xcvr->phy_clk); +stop_pll_ipg_clk: + clk_disable_unprepare(xcvr->pll_ipg_clk); +stop_ipg_clk: + clk_disable_unprepare(xcvr->ipg_clk); + + return ret; +} + +static const struct dev_pm_ops fsl_xcvr_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_xcvr_runtime_suspend, + fsl_xcvr_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver fsl_xcvr_driver = { + .probe = fsl_xcvr_probe, + .remove = fsl_xcvr_remove, + .driver = { + .name = "fsl,imx8mp-audio-xcvr", + .pm = &fsl_xcvr_pm_ops, + .of_match_table = fsl_xcvr_dt_ids, + }, +}; +module_platform_driver(fsl_xcvr_driver); + +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_DESCRIPTION("NXP Audio Transceiver (XCVR) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_xcvr.h b/sound/soc/fsl/fsl_xcvr.h new file mode 100644 index 000000000000..022fddc0310c --- /dev/null +++ b/sound/soc/fsl/fsl_xcvr.h @@ -0,0 +1,320 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * NXP XCVR ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright 2019 NXP + */ + +#ifndef __FSL_XCVR_H +#define __FSL_XCVR_H + +#include <linux/regmap.h> +#include <linux/reset.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_iec958.h> + +#define FSL_XCVR_MODE_SPDIF 0 +#define FSL_XCVR_MODE_ARC 1 +#define FSL_XCVR_MODE_EARC 2 + +/* XCVR Registers */ +#define FSL_XCVR_REG_OFFSET 0x800 /* regs offset */ +#define FSL_XCVR_FIFO_SIZE 0x80 /* 128 */ +#define FSL_XCVR_FIFO_WMK_RX (FSL_XCVR_FIFO_SIZE >> 1) /* 64 */ +#define FSL_XCVR_FIFO_WMK_TX (FSL_XCVR_FIFO_SIZE >> 1) /* 64 */ +#define FSL_XCVR_MAXBURST_RX (FSL_XCVR_FIFO_WMK_RX >> 2) /* 16 */ +#define FSL_XCVR_MAXBURST_TX (FSL_XCVR_FIFO_WMK_TX >> 2) /* 16 */ + +#define FSL_XCVR_RX_FIFO_ADDR 0x0C00 +#define FSL_XCVR_TX_FIFO_ADDR 0x0E00 + +#define FSL_XCVR_VERSION 0x00 /* Version */ +#define FSL_XCVR_EXT_CTRL 0x10 /* Control */ +#define FSL_XCVR_EXT_STATUS 0x20 /* Status */ +#define FSL_XCVR_EXT_IER0 0x30 /* Interrupt en 0 */ +#define FSL_XCVR_EXT_IER1 0x40 /* Interrupt en 1 */ +#define FSL_XCVR_EXT_ISR 0x50 /* Interrupt status */ +#define FSL_XCVR_EXT_ISR_SET 0x54 /* Interrupt status */ +#define FSL_XCVR_EXT_ISR_CLR 0x58 /* Interrupt status */ +#define FSL_XCVR_EXT_ISR_TOG 0x5C /* Interrupt status */ +#define FSL_XCVR_IER 0x70 /* Interrupt en for M0+ */ +#define FSL_XCVR_ISR 0x80 /* Interrupt status */ +#define FSL_XCVR_ISR_SET 0x84 /* Interrupt status set */ +#define FSL_XCVR_ISR_CLR 0x88 /* Interrupt status clear */ +#define FSL_XCVR_ISR_TOG 0x8C /* Interrupt status toggle */ +#define FSL_XCVR_PHY_AI_CTRL 0x90 +#define FSL_XCVR_PHY_AI_CTRL_SET 0x94 +#define FSL_XCVR_PHY_AI_CTRL_CLR 0x98 +#define FSL_XCVR_PHY_AI_CTRL_TOG 0x9C +#define FSL_XCVR_PHY_AI_WDATA 0xA0 +#define FSL_XCVR_PHY_AI_RDATA 0xA4 +#define FSL_XCVR_CLK_CTRL 0xB0 +#define FSL_XCVR_RX_DPTH_CTRL 0x180 /* RX datapath ctrl reg */ +#define FSL_XCVR_RX_DPTH_CTRL_SET 0x184 +#define FSL_XCVR_RX_DPTH_CTRL_CLR 0x188 +#define FSL_XCVR_RX_DPTH_CTRL_TOG 0x18c + +#define FSL_XCVR_RX_DPTH_CNTR_CTRL 0x1C0 +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_SET 0x1C4 +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_CLR 0x1C8 +#define FSL_XCVR_RX_DPTH_CNTR_CTRL_TOG 0x1CC + +#define FSL_XCVR_RX_DPTH_TSCR 0x1D0 +#define FSL_XCVR_RX_DPTH_BCR 0x1D4 +#define FSL_XCVR_RX_DPTH_BCTR 0x1D8 +#define FSL_XCVR_RX_DPTH_BCRR 0x1DC + +#define FSL_XCVR_TX_DPTH_CTRL 0x220 /* TX datapath ctrl reg */ +#define FSL_XCVR_TX_DPTH_CTRL_SET 0x224 +#define FSL_XCVR_TX_DPTH_CTRL_CLR 0x228 +#define FSL_XCVR_TX_DPTH_CTRL_TOG 0x22C +#define FSL_XCVR_TX_CS_DATA_0 0x230 /* TX channel status bits regs */ +#define FSL_XCVR_TX_CS_DATA_1 0x234 +#define FSL_XCVR_TX_CS_DATA_2 0x238 +#define FSL_XCVR_TX_CS_DATA_3 0x23C +#define FSL_XCVR_TX_CS_DATA_4 0x240 +#define FSL_XCVR_TX_CS_DATA_5 0x244 + +#define FSL_XCVR_TX_DPTH_CNTR_CTRL 0x260 +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_SET 0x264 +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_CLR 0x268 +#define FSL_XCVR_TX_DPTH_CNTR_CTRL_TOG 0x26C + +#define FSL_XCVR_TX_DPTH_TSCR 0x270 +#define FSL_XCVR_TX_DPTH_BCR 0x274 +#define FSL_XCVR_TX_DPTH_BCTR 0x278 +#define FSL_XCVR_TX_DPTH_BCRR 0x27C + +#define FSL_XCVR_DEBUG_REG_0 0x2E0 +#define FSL_XCVR_DEBUG_REG_1 0x2F0 + +#define FSL_XCVR_MAX_REG FSL_XCVR_DEBUG_REG_1 + +#define FSL_XCVR_EXT_CTRL_CORE_RESET BIT(31) + +#define FSL_XCVR_EXT_CTRL_RX_CMDC_RESET BIT(30) +#define FSL_XCVR_EXT_CTRL_TX_CMDC_RESET BIT(29) +#define FSL_XCVR_EXT_CTRL_CMDC_RESET(t) (t ? BIT(29) : BIT(30)) + +#define FSL_XCVR_EXT_CTRL_RX_DPTH_RESET BIT(28) +#define FSL_XCVR_EXT_CTRL_TX_DPTH_RESET BIT(27) +#define FSL_XCVR_EXT_CTRL_DPTH_RESET(t) (t ? BIT(27) : BIT(28)) + +#define FSL_XCVR_EXT_CTRL_TX_RX_MODE BIT(26) +#define FSL_XCVR_EXT_CTRL_DMA_RD_DIS BIT(25) +#define FSL_XCVR_EXT_CTRL_DMA_WR_DIS BIT(24) +#define FSL_XCVR_EXT_CTRL_DMA_DIS(t) (t ? BIT(24) : BIT(25)) +#define FSL_XCVR_EXT_CTRL_SPDIF_MODE BIT(23) +#define FSL_XCVR_EXT_CTRL_SLEEP_MODE BIT(21) + +#define FSL_XCVR_EXT_CTRL_TX_FWM_SHFT 0 +#define FSL_XCVR_EXT_CTRL_TX_FWM_MASK GENMASK(6, 0) +#define FSL_XCVR_EXT_CTRL_TX_FWM(i) (((i) << FSL_XCVR_EXT_CTRL_TX_FWM_SHFT) \ + & FSL_XCVR_EXT_CTRL_TX_FWM_MASK) +#define FSL_XCVR_EXT_CTRL_RX_FWM_SHFT 8 +#define FSL_XCVR_EXT_CTRL_RX_FWM_MASK GENMASK(14, 8) +#define FSL_XCVR_EXT_CTRL_RX_FWM(i) (((i) << FSL_XCVR_EXT_CTRL_RX_FWM_SHFT) \ + & FSL_XCVR_EXT_CTRL_RX_FWM_MASK) +#define FSL_XCVR_EXT_CTRL_PAGE_SHFT 16 +#define FSL_XCVR_EXT_CTRL_PAGE_MASK GENMASK(19, 16) +#define FSL_XCVR_EXT_CTRL_PAGE(i) (((i) << FSL_XCVR_EXT_CTRL_PAGE_SHFT) \ + & FSL_XCVR_EXT_CTRL_PAGE_MASK) + +#define FSL_XCVR_EXT_STUS_NT_FIFO_ENTR GENMASK(7, 0) +#define FSL_XCVR_EXT_STUS_NR_FIFO_ENTR GENMASK(15, 8) +#define FSL_XCVR_EXT_STUS_CM0_SLEEPING BIT(16) +#define FSL_XCVR_EXT_STUS_CM0_DEEP_SLP BIT(17) +#define FSL_XCVR_EXT_STUS_CM0_SLP_HACK BIT(18) +#define FSL_XCVR_EXT_STUS_RX_CMDC_RSTO BIT(23) +#define FSL_XCVR_EXT_STUS_TX_CMDC_RSTO BIT(24) +#define FSL_XCVR_EXT_STUS_RX_CMDC_COTO BIT(25) +#define FSL_XCVR_EXT_STUS_TX_CMDC_COTO BIT(26) +#define FSL_XCVR_EXT_STUS_HB_STATUS BIT(27) +#define FSL_XCVR_EXT_STUS_NEW_UD4_REC BIT(28) +#define FSL_XCVR_EXT_STUS_NEW_UD5_REC BIT(29) +#define FSL_XCVR_EXT_STUS_NEW_UD6_REC BIT(30) +#define FSL_XCVR_EXT_STUS_HPD_INPUT BIT(31) + +#define FSL_XCVR_IRQ_NEW_CS BIT(0) +#define FSL_XCVR_IRQ_NEW_UD BIT(1) +#define FSL_XCVR_IRQ_MUTE BIT(2) +#define FSL_XCVR_IRQ_CMDC_RESP_TO BIT(3) +#define FSL_XCVR_IRQ_ECC_ERR BIT(4) +#define FSL_XCVR_IRQ_PREAMBLE_MISMATCH BIT(5) +#define FSL_XCVR_IRQ_FIFO_UOFL_ERR BIT(6) +#define FSL_XCVR_IRQ_HOST_WAKEUP BIT(7) +#define FSL_XCVR_IRQ_HOST_OHPD BIT(8) +#define FSL_XCVR_IRQ_DMAC_NO_DATA_REC BIT(9) +#define FSL_XCVR_IRQ_DMAC_FMT_CHG_DET BIT(10) +#define FSL_XCVR_IRQ_HB_STATE_CHG BIT(11) +#define FSL_XCVR_IRQ_CMDC_STATUS_UPD BIT(12) +#define FSL_XCVR_IRQ_TEMP_UPD BIT(13) +#define FSL_XCVR_IRQ_DMA_RD_REQ BIT(14) +#define FSL_XCVR_IRQ_DMA_WR_REQ BIT(15) +#define FSL_XCVR_IRQ_DMAC_BME_BIT_ERR BIT(16) +#define FSL_XCVR_IRQ_PREAMBLE_MATCH BIT(17) +#define FSL_XCVR_IRQ_M_W_PRE_MISMATCH BIT(18) +#define FSL_XCVR_IRQ_B_PRE_MISMATCH BIT(19) +#define FSL_XCVR_IRQ_UNEXP_PRE_REC BIT(20) +#define FSL_XCVR_IRQ_ARC_MODE BIT(21) +#define FSL_XCVR_IRQ_CH_UD_OFLOW BIT(22) +#define FSL_XCVR_IRQ_EARC_ALL (FSL_XCVR_IRQ_NEW_CS | \ + FSL_XCVR_IRQ_NEW_UD | \ + FSL_XCVR_IRQ_MUTE | \ + FSL_XCVR_IRQ_FIFO_UOFL_ERR | \ + FSL_XCVR_IRQ_HOST_WAKEUP | \ + FSL_XCVR_IRQ_ARC_MODE) + +#define FSL_XCVR_ISR_CMDC_TX_EN BIT(3) +#define FSL_XCVR_ISR_HPD_TGL BIT(15) +#define FSL_XCVR_ISR_DMAC_SPARE_INT BIT(19) +#define FSL_XCVR_ISR_SET_SPDIF_RX_INT BIT(20) +#define FSL_XCVR_ISR_SET_SPDIF_TX_INT BIT(21) +#define FSL_XCVR_ISR_SET_SPDIF_MODE(t) (t ? BIT(21) : BIT(20)) +#define FSL_XCVR_ISR_SET_ARC_CM_INT BIT(22) +#define FSL_XCVR_ISR_SET_ARC_SE_INT BIT(23) + +#define FSL_XCVR_PHY_AI_ADDR_MASK GENMASK(7, 0) +#define FSL_XCVR_PHY_AI_RESETN BIT(15) +#define FSL_XCVR_PHY_AI_TOG_PLL BIT(24) +#define FSL_XCVR_PHY_AI_TOG_DONE_PLL BIT(25) +#define FSL_XCVR_PHY_AI_TOG_PHY BIT(26) +#define FSL_XCVR_PHY_AI_TOG_DONE_PHY BIT(27) +#define FSL_XCVR_PHY_AI_RW_MASK BIT(31) + +#define FSL_XCVR_RX_DPTH_CTRL_PAPB_FIFO_STATUS BIT(0) +#define FSL_XCVR_RX_DPTH_CTRL_DIS_PRE_ERR_CHK BIT(1) +#define FSL_XCVR_RX_DPTH_CTRL_DIS_NOD_REC_CHK BIT(2) +#define FSL_XCVR_RX_DPTH_CTRL_ECC_VUC_BIT_CHK BIT(3) +#define FSL_XCVR_RX_DPTH_CTRL_EN_CMP_PAR_CALC BIT(4) +#define FSL_XCVR_RX_DPTH_CTRL_RST_PKT_CNT_FIFO BIT(5) +#define FSL_XCVR_RX_DPTH_CTRL_STORE_FMT BIT(6) +#define FSL_XCVR_RX_DPTH_CTRL_EN_PAR_CALC BIT(7) +#define FSL_XCVR_RX_DPTH_CTRL_UDR BIT(8) +#define FSL_XCVR_RX_DPTH_CTRL_CSR BIT(9) +#define FSL_XCVR_RX_DPTH_CTRL_UDA BIT(10) +#define FSL_XCVR_RX_DPTH_CTRL_CSA BIT(11) +#define FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO BIT(12) +#define FSL_XCVR_RX_DPTH_CTRL_DIS_B_PRE_ERR_CHK BIT(13) +#define FSL_XCVR_RX_DPTH_CTRL_PABS BIT(19) +#define FSL_XCVR_RX_DPTH_CTRL_DTS_CDS BIT(20) +#define FSL_XCVR_RX_DPTH_CTRL_BLKC BIT(21) +#define FSL_XCVR_RX_DPTH_CTRL_MUTE_CTRL BIT(22) +#define FSL_XCVR_RX_DPTH_CTRL_MUTE_MODE BIT(23) +#define FSL_XCVR_RX_DPTH_CTRL_FMT_CHG_CTRL BIT(24) +#define FSL_XCVR_RX_DPTH_CTRL_FMT_CHG_MODE BIT(25) +#define FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL BIT(26) +#define FSL_XCVR_RX_DPTH_CTRL_LAYB_MODE BIT(27) +#define FSL_XCVR_RX_DPTH_CTRL_PRC BIT(28) +#define FSL_XCVR_RX_DPTH_CTRL_COMP BIT(29) +#define FSL_XCVR_RX_DPTH_CTRL_FSM GENMASK(31, 30) + +#define FSL_XCVR_TX_DPTH_CTRL_CS_ACK BIT(0) +#define FSL_XCVR_TX_DPTH_CTRL_UD_ACK BIT(1) +#define FSL_XCVR_TX_DPTH_CTRL_CS_MOD BIT(2) +#define FSL_XCVR_TX_DPTH_CTRL_UD_MOD BIT(3) +#define FSL_XCVR_TX_DPTH_CTRL_VLD_MOD BIT(4) +#define FSL_XCVR_TX_DPTH_CTRL_FRM_VLD BIT(5) +#define FSL_XCVR_TX_DPTH_CTRL_EN_PARITY BIT(6) +#define FSL_XCVR_TX_DPTH_CTRL_EN_PREAMBLE BIT(7) +#define FSL_XCVR_TX_DPTH_CTRL_EN_ECC_INTER BIT(8) +#define FSL_XCVR_TX_DPTH_CTRL_BYPASS_FEM BIT(10) +#define FSL_XCVR_TX_DPTH_CTRL_FRM_FMT BIT(11) +#define FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX BIT(14) +#define FSL_XCVR_TX_DPTH_CTRL_ADD_CYC_TX_OE_STR BIT(15) +#define FSL_XCVR_TX_DPTH_CTRL_ADD_CYC_TX_OE_END BIT(16) +#define FSL_XCVR_TX_DPTH_CTRL_CLK_RATIO BIT(29) +#define FSL_XCVR_TX_DPTH_CTRL_TM_NO_PRE_BME GENMASK(31, 30) + +#define FSL_XCVR_PHY_AI_CTRL_AI_RESETN BIT(15) + +#define FSL_XCVR_PLL_CTRL0 0x00 +#define FSL_XCVR_PLL_CTRL0_SET 0x04 +#define FSL_XCVR_PLL_CTRL0_CLR 0x08 +#define FSL_XCVR_PLL_NUM 0x20 +#define FSL_XCVR_PLL_DEN 0x30 +#define FSL_XCVR_PLL_PDIV 0x40 +#define FSL_XCVR_PLL_BANDGAP_SET 0x54 +#define FSL_XCVR_PHY_CTRL 0x00 +#define FSL_XCVR_PHY_CTRL_SET 0x04 +#define FSL_XCVR_PHY_CTRL_CLR 0x08 +#define FSL_XCVR_PHY_CTRL2 0x70 +#define FSL_XCVR_PHY_CTRL2_SET 0x74 +#define FSL_XCVR_PHY_CTRL2_CLR 0x78 + +#define FSL_XCVR_PLL_BANDGAP_EN_VBG BIT(0) +#define FSL_XCVR_PLL_CTRL0_HROFF BIT(13) +#define FSL_XCVR_PLL_CTRL0_PWP BIT(14) +#define FSL_XCVR_PLL_CTRL0_CM0_EN BIT(24) +#define FSL_XCVR_PLL_CTRL0_CM1_EN BIT(25) +#define FSL_XCVR_PLL_CTRL0_CM2_EN BIT(26) +#define FSL_XCVR_PLL_PDIVx(v, i) ((v & 0x7) << (4 * i)) + +#define FSL_XCVR_PHY_CTRL_PHY_EN BIT(0) +#define FSL_XCVR_PHY_CTRL_RX_CM_EN BIT(1) +#define FSL_XCVR_PHY_CTRL_TSDIFF_OE BIT(5) +#define FSL_XCVR_PHY_CTRL_SPDIF_EN BIT(8) +#define FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN BIT(9) +#define FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN BIT(10) +#define FSL_XCVR_PHY_CTRL_TX_CLK_MASK GENMASK(26, 25) +#define FSL_XCVR_PHY_CTRL_TX_CLK_HDMI_SS BIT(25) +#define FSL_XCVR_PHY_CTRL_TX_CLK_AUD_SS BIT(26) +#define FSL_XCVR_PHY_CTRL2_EARC_TXMS BIT(14) + +#define FSL_XCVR_CS_DATA_0_FS_MASK GENMASK(31, 24) +#define FSL_XCVR_CS_DATA_0_FS_32000 0x3000000 +#define FSL_XCVR_CS_DATA_0_FS_44100 0x0000000 +#define FSL_XCVR_CS_DATA_0_FS_48000 0x2000000 +#define FSL_XCVR_CS_DATA_0_FS_64000 0xB000000 +#define FSL_XCVR_CS_DATA_0_FS_88200 0x8000000 +#define FSL_XCVR_CS_DATA_0_FS_96000 0xA000000 +#define FSL_XCVR_CS_DATA_0_FS_176400 0xC000000 +#define FSL_XCVR_CS_DATA_0_FS_192000 0xE000000 + +#define FSL_XCVR_CS_DATA_0_CH_MASK 0x3A +#define FSL_XCVR_CS_DATA_0_CH_U2LPCM 0x00 +#define FSL_XCVR_CS_DATA_0_CH_UMLPCM 0x20 +#define FSL_XCVR_CS_DATA_0_CH_U1BAUD 0x30 + +#define FSL_XCVR_CS_DATA_1_CH_MASK 0xF000 +#define FSL_XCVR_CS_DATA_1_CH_2 0x0000 +#define FSL_XCVR_CS_DATA_1_CH_8 0x7000 +#define FSL_XCVR_CS_DATA_1_CH_16 0xB000 +#define FSL_XCVR_CS_DATA_1_CH_32 0x3000 + +/* Data memory structures */ +#define FSL_XCVR_RX_CS_CTRL_0 0x20 /* First RX CS control register */ +#define FSL_XCVR_RX_CS_CTRL_1 0x24 /* Second RX CS control register */ +#define FSL_XCVR_RX_CS_BUFF_0 0x80 /* First RX CS buffer */ +#define FSL_XCVR_RX_CS_BUFF_1 0xA0 /* Second RX CS buffer */ +#define FSL_XCVR_CAP_DATA_STR 0x300 /* Capabilities data structure */ + +#define FSL_XCVR_CAPDS_SIZE 256 + +struct fsl_xcvr_soc_data { + const char *fw_name; +}; + +struct fsl_xcvr { + const struct fsl_xcvr_soc_data *soc_data; + struct platform_device *pdev; + struct regmap *regmap; + struct clk *ipg_clk; + struct clk *pll_ipg_clk; + struct clk *phy_clk; + struct clk *spba_clk; + struct reset_control *reset; + u8 streams; + u32 mode; + u32 arc_mode; + void __iomem *ram_addr; + struct snd_dmaengine_dai_dma_data dma_prms_rx; + struct snd_dmaengine_dai_dma_data dma_prms_tx; + struct snd_aes_iec958 rx_iec958; + struct snd_aes_iec958 tx_iec958; + u8 cap_ds[FSL_XCVR_CAPDS_SIZE]; +}; + +const struct attribute_group *fsl_xcvr_get_attr_grp(void); + +#endif /* __FSL_XCVR_H */ diff --git a/sound/soc/fsl/fsl_xcvr_sysfs.c b/sound/soc/fsl/fsl_xcvr_sysfs.c new file mode 100644 index 000000000000..77f425b88b86 --- /dev/null +++ b/sound/soc/fsl/fsl_xcvr_sysfs.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2020 NXP Semiconductors. + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/kdev_t.h> +#include <linux/pm_runtime.h> + +#include "fsl_xcvr.h" + +static ssize_t read(struct device *dev, struct device_attribute *attr, char *buf, unsigned int reg) +{ + struct fsl_xcvr *xcvr = dev_get_drvdata(dev); + unsigned int val = 0; + + regmap_read(xcvr->regmap, reg, &val); + + return sprintf(buf, "%u\n", val); +} + +#define XRDC_RO_ATTR(_name, _reg) \ +static ssize_t _name##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ /* read bitcounter */ \ + return read(dev, attr, buf, _reg); \ +} \ +static DEVICE_ATTR_RO(_name); + +XRDC_RO_ATTR(rx_bitcnt, FSL_XCVR_RX_DPTH_BCR) +XRDC_RO_ATTR(tx_bitcnt, FSL_XCVR_TX_DPTH_BCR) +XRDC_RO_ATTR(rx_timestamp, FSL_XCVR_RX_DPTH_TSCR) +XRDC_RO_ATTR(tx_timestamp, FSL_XCVR_TX_DPTH_TSCR) +XRDC_RO_ATTR(rx_bitcnt_latched_timestamp, FSL_XCVR_RX_DPTH_BCRR) +XRDC_RO_ATTR(tx_bitcnt_latched_timestamp, FSL_XCVR_TX_DPTH_BCRR) + +/* ============ */ +static ssize_t show(struct device *dev, struct device_attribute *attr, char *buf, u32 reg, u32 bit) +{ + struct fsl_xcvr *xcvr = dev_get_drvdata(dev); + unsigned int val = 0; + + regmap_read(xcvr->regmap, reg, &val); + + return sprintf(buf, "%u\n", (val & BIT(bit)) ? 1 : 0); +} + +static ssize_t store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n, + u32 reg, u32 bit) +{ + struct fsl_xcvr *xcvr = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + regmap_update_bits(xcvr->regmap, reg, BIT(bit), val ? BIT(bit) : 0); + + return n; +} + +#define XRDC_RW_ATTR(_name, _reg, _bit) \ +static ssize_t _name##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + return show(dev, attr, buf, _reg, _bit); \ +} \ +\ +static ssize_t _name##_store(struct device *dev, struct device_attribute *attr, const char *buf, \ + size_t n) \ +{ \ + return store(dev, attr, buf, n, _reg, _bit); \ +} \ +static DEVICE_ATTR_RW(_name); + +XRDC_RW_ATTR(rx_bitcnt_reset, FSL_XCVR_RX_DPTH_CNTR_CTRL, 8) +XRDC_RW_ATTR(tx_bitcnt_reset, FSL_XCVR_TX_DPTH_CNTR_CTRL, 8) +XRDC_RW_ATTR(rx_timestamp_enable, FSL_XCVR_RX_DPTH_CNTR_CTRL, 0) +XRDC_RW_ATTR(tx_timestamp_enable, FSL_XCVR_TX_DPTH_CNTR_CTRL, 0) +XRDC_RW_ATTR(rx_timestamp_increment, FSL_XCVR_RX_DPTH_CNTR_CTRL, 1) +XRDC_RW_ATTR(tx_timestamp_increment, FSL_XCVR_TX_DPTH_CNTR_CTRL, 1) +XRDC_RW_ATTR(rx_timestamp_reset, FSL_XCVR_RX_DPTH_CNTR_CTRL, 9) +XRDC_RW_ATTR(tx_timestamp_reset, FSL_XCVR_TX_DPTH_CNTR_CTRL, 9) + +static struct attribute *fsl_xcvr_attrs[] = { + &dev_attr_tx_bitcnt.attr, + &dev_attr_rx_bitcnt.attr, + &dev_attr_tx_timestamp.attr, + &dev_attr_rx_timestamp.attr, + &dev_attr_tx_bitcnt_latched_timestamp.attr, + &dev_attr_rx_bitcnt_latched_timestamp.attr, + &dev_attr_tx_timestamp_enable.attr, + &dev_attr_rx_timestamp_enable.attr, + &dev_attr_tx_timestamp_increment.attr, + &dev_attr_rx_timestamp_increment.attr, + &dev_attr_tx_bitcnt_reset.attr, + &dev_attr_rx_bitcnt_reset.attr, + &dev_attr_tx_timestamp_reset.attr, + &dev_attr_rx_timestamp_reset.attr, + NULL, +}; + +static struct attribute_group fsl_xcvr_attr_group = { + .name = "counters", + .attrs = fsl_xcvr_attrs, +}; + +const struct attribute_group *fsl_xcvr_get_attr_grp() +{ + return &fsl_xcvr_attr_group; +} diff --git a/sound/soc/fsl/hdmi_pcm.S b/sound/soc/fsl/hdmi_pcm.S new file mode 100644 index 000000000000..d8d95fd8f42f --- /dev/null +++ b/sound/soc/fsl/hdmi_pcm.S @@ -0,0 +1,246 @@ +/** + * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +.section .text + +.global hdmi_dma_copy_16_neon_lut +.global hdmi_dma_copy_16_neon_fast +.global hdmi_dma_copy_24_neon_lut +.global hdmi_dma_copy_24_neon_fast + + +/** + * hdmi_dma_copy_16_neon_lut + * Convert pcm sample to iec sample. Pcm sample is 16 bits. + * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst, + * int samples, unsigned char *lookup_table); + * Return value + * None + * Parameters + * src Source PCM16 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + * lookup_table Preconstructed header table. Channels interleaved. + */ + +hdmi_dma_copy_16_neon_lut: + mov r12, #1 /* construct vector(1) */ + vdup.8 d6, r12 + +hdmi_dma_copy_16_neon_lut_start: + + /* get 8 samples to q0 */ + vld1.16 {d0, d1}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q1, q0 /* count of 1s */ + vpadd.i8 d2, d2, d3 /* only care about the LST in every element */ + vand d2, d2, d6 /* clear other bits while keep the least bit */ + vshl.u8 d2, d2, #3 /* bit p: d2 = d2 << 3 */ + + /* get packet header */ + vld1.8 {d5}, [r3]! + veor d4, d5, d2 /* xor bit c */ + + /* store: (d4 << 16 | q0) << 8 */ + vmovl.u8 q2, d4 /* expand from char to short */ + vzip.16 q0, q2 + vshl.u32 q0, q0, #8 + vshl.u32 q1, q2, #8 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_16_neon_lut_start + + mov pc, lr + +/** + * hdmi_dma_copy_16_neon_fast + * Convert pcm sample to iec sample. Pcm sample is 16 bits. + * Frame index's between 48 and 191 inclusively. + * Channel count can be 1, 2, 4 or 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_16_neon_fast(unsigned short *src, + * unsigned int *dst, int samples); + * Return value + * None + * Parameters + * src Source PCM16 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + */ + +hdmi_dma_copy_16_neon_fast: + mov r12, #1 /* construct vector(1) */ + vdup.8 d6, r12 + +hdmi_dma_copy_16_neon_fast_start: + /* get 8 samples to q0 */ + vld1.16 {d0, d1}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q1, q0 /* count of 1s */ + vpadd.i8 d2, d2, d3 + vand d2, d2, d6 /* clear other bits while keep the LST */ + /* finally we construct packet header */ + vshl.u8 d4, d2, #3 /* bit p: d2 = d2 << 3 */ + + /* get packet header: always 0 */ + + /* store: (d4 << 16 | q0) << 8 */ + vmovl.u8 q2, d4 /* expand from char to short */ + vzip.16 q0, q2 + vshl.u32 q0, q0, #8 + vshl.u32 q1, q2, #8 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_16_neon_fast_start + + mov pc, lr + + + +/** + * hdmi_dma_copy_24_neon_lut + * Convert pcm sample to iec sample. Pcm sample is 24 bits. + * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst, + * int samples, unsigned char *lookup_table); + * Return value + * None + * Parameters + * src Source PCM24 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + * lookup_table Preconstructed header table. Channels interleaved. + */ + +hdmi_dma_copy_24_neon_lut: + vpush {d8} + + mov r12, #1 /* construct vector(1) */ + vdup.8 d8, r12 + +hdmi_dma_copy_24_neon_lut_start: + + /* get 8 samples to q0 and q1 */ + vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q2, q0 /* count of 1s */ + vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */ + vcnt.8 q3, q1 + vpadd.i8 d6, d6, d7 + vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */ + vand d4, d4, d8 /* clear other bits while keep the least bit */ + vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */ + + /* get packet header */ + vld1.8 {d5}, [r3]!/* d5: original header */ + veor d5, d5, d4 /* fix bit p */ + + /* store: (d5 << 24 | q0) */ + vmovl.u8 q3, d5 /* expand from char to short */ + vmovl.u16 q2, d6 /* expand from short to int */ + vmovl.u16 q3, d7 + vshl.u32 q2, q2, #24 + vshl.u32 q3, q3, #24 + vorr q0, q0, q2 + vorr q1, q1, q3 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_24_neon_lut_start + + vpop {d8} + mov pc, lr + +/** + * hdmi_dma_copy_24_neon_fast + * Convert pcm sample to iec sample. Pcm sample is 24 bits. + * Frame index's between 48 and 191 inclusively. + * Channel count can be 1, 2, 4 or 8. + * Frame count should be multipliable by 4, and Sample count by 8. + * + * C Prototype + * void hdmi_dma_copy_24_neon_fast(unsigned int *src, + * unsigned int *dst, int samples); + * Return value + * None + * Parameters + * src Source PCM24 samples + * dst Dest buffer to store pcm with header + * samples Contains sample count (=frame_count * channel_count) + */ + +hdmi_dma_copy_24_neon_fast: + vpush {d8} + + mov r12, #1 /* construct vector(1) */ + vdup.8 d8, r12 + +hdmi_dma_copy_24_neon_fast_start: + /* get 8 samples to q0 and q1 */ + vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */ + + /* pld [r1, #(64*4)] */ + + /* xor every bit */ + vcnt.8 q2, q0 /* count of 1s */ + vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */ + vcnt.8 q3, q1 + vpadd.i8 d6, d6, d7 + vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */ + vand d4, d4, d8 /* clear other bits while keep the least bit */ + vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */ + + /* store: (d4 << 24 | q0) */ + vmovl.u8 q3, d4 /* expand from char to short */ + vmovl.u16 q2, d6 /* expand from short to int */ + vmovl.u16 q3, d7 + vshl.u32 q2, q2, #24 + vshl.u32 q3, q3, #24 + vorr q0, q0, q2 + vorr q1, q1, q3 + vst1.32 {d0, d1, d2, d3}, [r1]! + + /* decrease sample count */ + subs r2, r2, #8 + bne hdmi_dma_copy_24_neon_fast_start + + vpop {d8} + mov pc, lr diff --git a/sound/soc/fsl/imx-ak4458.c b/sound/soc/fsl/imx-ak4458.c new file mode 100644 index 000000000000..9605d9d74af6 --- /dev/null +++ b/sound/soc/fsl/imx-ak4458.c @@ -0,0 +1,417 @@ +/* i.MX AK4458 audio support + * + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" +#include "fsl_dsd.h" + +struct imx_ak4458_data { + struct snd_soc_card card; + int num_codec_conf; + struct snd_soc_codec_conf *codec_conf; + bool tdm_mode; + int pdn_gpio; + unsigned int slots; + unsigned int slot_width; + bool one2one_ratio; +}; + +static struct snd_soc_dapm_widget imx_ak4458_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +/** + * Tables 3 & 4 - mapping LRCK fs and frame width + */ +static const struct imx_ak4458_fs_map { + unsigned int rmin; + unsigned int rmax; + unsigned int wmin; + unsigned int wmax; +} fs_map[] = { + /* Normal, < 32kHz */ + { .rmin = 8000, .rmax = 24000, .wmin = 1024, .wmax = 1024, }, + /* Normal, 32kHz */ + { .rmin = 32000, .rmax = 32000, .wmin = 256, .wmax = 1024, }, + /* Normal */ + { .rmin = 44100, .rmax = 48000, .wmin = 256, .wmax = 768, }, + /* Double */ + { .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 512, }, + /* Quad */ + { .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 256, }, + /* Oct */ + { .rmin = 352800, .rmax = 384000, .wmin = 32, .wmax = 128, }, + /* Hex */ + { .rmin = 705600, .rmax = 768000, .wmin = 16, .wmax = 64, }, +}; + +static const struct imx_ak4458_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +} fs_mul_tdm[] = { + /* + * Table 13 - Audio Interface Format + * For TDM mode, MCLK should is set to + * obtained from 2 * slots * slot_width + */ + { .min = 128, .max = 128, .mul = 256 }, /* TDM128 */ + { .min = 256, .max = 256, .mul = 512 }, /* TDM256 */ + { .min = 512, .max = 512, .mul = 1024 }, /* TDM512 */ +}; + +static const u32 ak4458_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static const u32 ak4458_rates_tdm[] = { + 8000, 16000, 32000, + 48000, 96000, +}; + +static const u32 ak4458_channels[] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, +}; + +static const u32 ak4458_channels_tdm[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 16, +}; + +static unsigned long ak4458_get_mclk_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(rtd->card); + unsigned int rate = params_rate(params); + unsigned int width = data->slots * data->slot_width; + int i, mode; + + if (data->tdm_mode) { + /* can be 128, 256 or 512 */ + mode = data->slots * data->slot_width; + + for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) { + /* min = max = slots * slots_width */ + if (mode != fs_mul_tdm[i].min) + continue; + return rate * fs_mul_tdm[i].mul; + } + } else { + for (i = 0; i < ARRAY_SIZE(fs_map); i++) { + if (rate >= fs_map[i].rmin && rate <= fs_map[i].rmax) { + width = max(width, fs_map[i].wmin); + width = min(width, fs_map[i].wmax); + + /* Adjust SAI bclk:mclk ratio */ + width *= data->one2one_ratio ? 1 : 2; + + return rate * width; + } + } + } + + /* Let DAI manage clk frequency by default */ + return 0; +} + +static int imx_aif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + unsigned long mclk_freq; + bool is_dsd = fsl_is_dsd(params); + int ret, i; + + if (is_dsd) { + channels = 1; + data->slots = 1; + data->slot_width = params_width(params); + fmt |= SND_SOC_DAIFMT_PDM; + } else if (data->tdm_mode) { + data->slots = 8; + data->slot_width = 32; + fmt |= SND_SOC_DAIFMT_DSP_B; + } else { + data->slots = 2; + data->slot_width = params_physical_width(params); + fmt |= SND_SOC_DAIFMT_I2S; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai[%d] fmt: %d\n", + i, ret); + return ret; + } + ret = snd_soc_dai_set_tdm_slot(codec_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set codec dai[%d] tdm slot: %d\n", + i, ret); + return ret; + } + } + + /* set MCLK freq */ + mclk_freq = ak4458_get_mclk_rate(substream, params); + if (is_dsd) + mclk_freq = 22579200; + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(dev, "failed to set cpui dai mclk1 rate (%lu): %d\n", + mclk_freq, ret); + return ret; +} + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card); + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + if (data->tdm_mode) { + constraint_channels.list = ak4458_channels_tdm; + constraint_channels.count = ARRAY_SIZE(ak4458_channels_tdm); + constraint_rates.list = ak4458_rates_tdm; + constraint_rates.count = ARRAY_SIZE(ak4458_rates_tdm); + } else { + constraint_channels.list = ak4458_channels; + constraint_channels.count = ARRAY_SIZE(ak4458_channels); + constraint_rates.list = ak4458_rates; + constraint_rates.count = ARRAY_SIZE(ak4458_rates); + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static struct snd_soc_ops imx_aif_ops = { + .hw_params = imx_aif_hw_params, + .startup = imx_aif_startup, +}; + +static struct snd_soc_dai_link_component ak4458_codecs[] = { + { + /* Playback */ + .dai_name = "ak4458-aif", + }, + { + /* Capture */ + .dai_name = "ak4458-aif", + }, +}; + +static struct snd_soc_dai_link imx_ak4458_dai = { + .name = "ak4458", + .stream_name = "Audio", + .codecs = ak4458_codecs, + .num_codecs = 2, + .ignore_pmdown_time = 1, + .ops = &imx_aif_ops, + .playback_only = 1, +}; + +static int imx_ak4458_probe(struct platform_device *pdev) +{ + struct imx_ak4458_data *priv; + struct device_node *cpu_np, *codec_np_0 = NULL, *codec_np_1 = NULL; + struct platform_device *cpu_pdev; + struct snd_soc_dai_link_component *dlc; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dlc = devm_kzalloc(&pdev->dev, 2 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np_0 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np_0) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np_1 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 1); + if (!codec_np_1) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL)) + priv->tdm_mode = true; + + priv->num_codec_conf = 2; + priv->codec_conf = devm_kzalloc(&pdev->dev, + priv->num_codec_conf * sizeof(struct snd_soc_codec_conf), + GFP_KERNEL); + if (!priv->codec_conf) { + ret = -ENOMEM; + goto fail; + } + + priv->codec_conf[0].name_prefix = "0"; + priv->codec_conf[0].of_node = codec_np_0; + priv->codec_conf[1].name_prefix = "1"; + priv->codec_conf[1].of_node = codec_np_1; + + ak4458_codecs[0].of_node = codec_np_0; + ak4458_codecs[1].of_node = codec_np_1; + + imx_ak4458_dai.cpus = &dlc[0]; + imx_ak4458_dai.num_cpus = 1; + imx_ak4458_dai.platforms = &dlc[1]; + imx_ak4458_dai.num_platforms = 1; + + imx_ak4458_dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_ak4458_dai.platforms->of_node = cpu_np; + + priv->card.num_links = 1; + priv->card.dai_link = &imx_ak4458_dai; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak4458_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4458_dapm_widgets); + priv->card.codec_conf = priv->codec_conf; + priv->card.num_configs = priv->num_codec_conf; + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak4458-mq"); + + priv->pdn_gpio = of_get_named_gpio(pdev->dev.of_node, "ak4458,pdn-gpio", 0); + if (gpio_is_valid(priv->pdn_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, priv->pdn_gpio, + GPIOF_OUT_INIT_LOW, "ak4458,pdn"); + if (ret) { + dev_err(&pdev->dev, "unable to get pdn gpio\n"); + goto fail; + } + + gpio_set_value_cansleep(priv->pdn_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(priv->pdn_gpio, 1); + usleep_range(1000, 2000); + } + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np_0) + of_node_put(codec_np_0); + if (codec_np_1) + of_node_put(codec_np_1); + + return ret; +} + +static const struct of_device_id imx_ak4458_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak4458", }, + { .compatible = "fsl,imx-audio-ak4458-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak4458_dt_ids); + +static struct platform_driver imx_ak4458_driver = { + .driver = { + .name = "imx-ak4458", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak4458_dt_ids, + }, + .probe = imx_ak4458_probe, +}; +module_platform_driver(imx_ak4458_driver); + +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK4458 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak4458"); diff --git a/sound/soc/fsl/imx-ak4497.c b/sound/soc/fsl/imx-ak4497.c new file mode 100644 index 000000000000..a4d24333026c --- /dev/null +++ b/sound/soc/fsl/imx-ak4497.c @@ -0,0 +1,269 @@ +/* i.MX AK4458 audio support + * + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" + +struct imx_ak4497_data { + struct snd_soc_card card; + bool one2one_ratio; +}; + +static struct snd_soc_dapm_widget imx_ak4497_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), +}; + +static const struct imx_ak4497_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +} fs_mul[] = { + /** + * Table 7 - mapping multiplier and speed mode + * Tables 8 & 9 - mapping speed mode and LRCK fs + */ + { .min = 8000, .max = 32000, .mul = 1024 }, /* Normal, <= 32kHz */ + { .min = 44100, .max = 48000, .mul = 512 }, /* Normal */ + { .min = 88200, .max = 96000, .mul = 256 }, /* Double */ + { .min = 176400, .max = 192000, .mul = 128 }, /* Quad */ + { .min = 352800, .max = 384000, .mul = 2*64 }, /* Oct */ + { .min = 705600, .max = 768000, .mul = 2*32 }, /* Hex */ +}; + +static bool imx_ak4497_is_dsd(struct snd_pcm_hw_params *params) +{ + snd_pcm_format_t format = params_format(params); + + switch (format) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + return true; + default: + return false; + } +} + +static unsigned long imx_ak4497_compute_freq(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + int i; + + /* Find the appropriate MCLK freq */ + for (i = 0; i < ARRAY_SIZE(fs_mul); i++) { + if (rate >= fs_mul[i].min && rate <= fs_mul[i].max) + return rate * fs_mul[i].mul; + } + + /* Let DAI manage MCLK frequency */ + return 0; +} + +static int imx_aif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak4497_data *priv = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + unsigned long freq = imx_ak4497_compute_freq(substream, params); + bool is_dsd = imx_ak4497_is_dsd(params); + int ret; + + fmt |= (is_dsd ? SND_SOC_DAIFMT_PDM : SND_SOC_DAIFMT_I2S); + + if (is_dsd && freq > 22579200 && priv->one2one_ratio) + freq = 22579200; + + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(dev, "failed to set cpu dai mclk1 rate(%lu): %d\n", + freq, ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (is_dsd) + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + 0x1, 0x1, + 1, params_width(params)); + else + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + 2, params_physical_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return ret; +} + +static const u32 support_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + static struct snd_pcm_hw_constraint_list constraint_rates; + + constraint_rates.list = support_rates; + constraint_rates.count = ARRAY_SIZE(support_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return ret; +} + +static struct snd_soc_ops imx_aif_ops = { + .startup = imx_aif_startup, + .hw_params = imx_aif_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak4497-aif")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link imx_ak4497_dai = { + .name = "ak4497", + .stream_name = "Audio", + .ops = &imx_aif_ops, + .playback_only = 1, + SND_SOC_DAILINK_REG(hifi), +}; + +static int imx_ak4497_probe(struct platform_device *pdev) +{ + struct imx_ak4497_data *priv; + struct device_node *cpu_np, *codec_np = NULL; + struct platform_device *cpu_pdev; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + imx_ak4497_dai.codecs->of_node = codec_np; + imx_ak4497_dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_ak4497_dai.platforms->of_node = cpu_np; + imx_ak4497_dai.playback_only = 1; + + priv->card.dai_link = &imx_ak4497_dai; + priv->card.num_links = 1; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak4497_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4497_dapm_widgets); + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak4497-mq"); + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_ak4497_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak4497", }, + { .compatible = "fsl,imx-audio-ak4497-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak4497_dt_ids); + +static struct platform_driver imx_ak4497_driver = { + .driver = { + .name = "imx-ak4497", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak4497_dt_ids, + }, + .probe = imx_ak4497_probe, +}; +module_platform_driver(imx_ak4497_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK4497 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak4497"); diff --git a/sound/soc/fsl/imx-ak5558.c b/sound/soc/fsl/imx-ak5558.c new file mode 100644 index 000000000000..908b6d030dae --- /dev/null +++ b/sound/soc/fsl/imx-ak5558.c @@ -0,0 +1,559 @@ +/* i.MX AK5558 audio support + * + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" +#include "../codecs/ak5558.h" + + +struct imx_ak5558_data { + struct snd_soc_card card; + bool tdm_mode; + unsigned long slots; + unsigned long slot_width; + bool one2one_ratio; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; +}; + +/* + * imx_ack5558_fs_mul - sampling frequency multiplier + * + * min <= fs <= max, MCLK = mul * LRCK + */ +struct imx_ak5558_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +}; + +/* + * Auto MCLK selection based on LRCK for Normal Mode + * (Table 4 from datasheet) + */ +static const struct imx_ak5558_fs_mul fs_mul[] = { + { .min = 8000, .max = 32000, .mul = 1024 }, + { .min = 44100, .max = 48000, .mul = 512 }, + { .min = 88200, .max = 96000, .mul = 256 }, + { .min = 176400, .max = 192000, .mul = 128 }, + { .min = 352800, .max = 384000, .mul = 64 }, + { .min = 705600, .max = 768000, .mul = 32 }, +}; + +/* + * MCLK and BCLK selection based on TDM mode + * because of SAI we also add the restriction: MCLK >= 2 * BCLK + * (Table 9 from datasheet) + */ +static const struct imx_ak5558_fs_mul fs_mul_tdm[] = { + { .min = 128, .max = 128, .mul = 256 }, + { .min = 256, .max = 256, .mul = 512 }, + { .min = 512, .max = 512, .mul = 1024 }, +}; + +static struct snd_soc_dapm_widget imx_ak5558_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const u32 ak5558_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, +}; + +static const u32 ak5558_tdm_rates[] = { + 8000, 16000, 32000, + 48000, 96000 +}; + +static const u32 ak5558_channels[] = { + 1, 2, 4, 6, 8, +}; + +static const u32 ak5552_channels[] = { + 1, 2, +}; + +static unsigned long ak5558_get_mclk_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(rtd->card); + unsigned int rate = params_rate(params); + unsigned int freq = 0; /* Let DAI manage clk frequency by default */ + int mode; + int i; + + if (data->tdm_mode) { + mode = data->slots * data->slot_width; + + for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) { + /* min = max = slots * slots_width */ + if (mode != fs_mul_tdm[i].min) + continue; + freq = rate * fs_mul_tdm[i].mul; + break; + } + } else { + for (i = 0; i < ARRAY_SIZE(fs_mul); i++) { + if (rate < fs_mul[i].min || rate > fs_mul[i].max) + continue; + freq = rate * fs_mul[i].mul; + break; + } + } + + return freq; +} + +static int imx_aif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned long mclk_freq; + unsigned int fmt; + int ret; + + if (data->tdm_mode) + fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (data->tdm_mode) { + /* support TDM256 (8 slots * 32 bits/per slot) */ + data->slots = 8; + data->slot_width = 32; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(codec_dai, + BIT(channels) - 1, BIT(channels) - 1, + 8, 32); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + } else { + /* normal mode (I2S) */ + data->slots = 2; + data->slot_width = params_physical_width(params); + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + } + + mclk_freq = ak5558_get_mclk_rate(substream, params); + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(dev, "failed to set cpu_dai mclk1 rate %lu\n", + mclk_freq); + + return ret; +} + +/* In order to support odd channels, force tdm mode for FE-BE case */ +static int imx_aif_hw_params_be(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned long mclk_freq; + unsigned int fmt; + int ret; + + fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + /* support TDM256 (8 slots * 32 bits/per slot) */ + data->slots = 8; + data->slot_width = 32; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(codec_dai, + BIT(channels) - 1, BIT(channels) - 1, + 8, 32); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + mclk_freq = ak5558_get_mclk_rate(substream, params); + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(dev, "failed to set cpu_dai mclk1 rate %lu\n", + mclk_freq); + + return ret; +} + +static int imx_ak5558_hw_rule_rate(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_rule *r) +{ + struct imx_ak5558_data *data = r->private; + struct snd_interval t = { .min = 8000, .max = 8000, }; + unsigned int fs; + unsigned long mclk_freq; + int i; + + fs = hw_param_interval(p, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; + fs *= data->tdm_mode ? 8 : 2; + + /* Identify maximum supported rate */ + for (i = 0; i < ARRAY_SIZE(ak5558_rates); i++) { + mclk_freq = fs * ak5558_rates[i]; + /* Adjust SAI bclk:mclk ratio */ + mclk_freq *= data->one2one_ratio ? 1 : 2; + + /* Skip rates for which MCLK is beyond supported value */ + if (mclk_freq > 36864000) + continue; + + if (t.max < ak5558_rates[i]) + t.max = ak5558_rates[i]; + } + + return snd_interval_refine(hw_param_interval(p, r->var), &t); +} + +static int imx_aif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card); + + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + if (data->tdm_mode) { + constraint_rates.list = ak5558_tdm_rates; + constraint_rates.count = ARRAY_SIZE(ak5558_tdm_rates); + } else { + constraint_rates.list = ak5558_rates; + constraint_rates.count = ARRAY_SIZE(ak5558_rates); + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + if (!data->tdm_mode) { + if (of_device_is_compatible(card->dev->of_node, + "fsl,imx-audio-ak5552")) { + constraint_channels.list = ak5552_channels; + constraint_channels.count = ARRAY_SIZE(ak5552_channels); + } else { + constraint_channels.list = ak5558_channels; + constraint_channels.count = ARRAY_SIZE(ak5558_channels); + } + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret < 0) + return ret; + } + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, imx_ak5558_hw_rule_rate, data, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); +} + +static struct snd_soc_ops imx_aif_ops = { + .hw_params = imx_aif_hw_params, + .startup = imx_aif_startup, +}; + +static struct snd_soc_ops imx_aif_ops_be = { + .hw_params = imx_aif_hw_params_be, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_ak5558_data *priv = snd_soc_card_get_drvdata(card); + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = priv->asrc_rate; + rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak5558-aif")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak5558-aif")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link imx_ak5558_dai[] = { + { + .name = "ak5558", + .stream_name = "Audio", + .ops = &imx_aif_ops, + .capture_only = 1, + SND_SOC_DAILINK_REG(hifi), + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 0, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 0, + .dpcm_capture = 1, + .ops = &imx_aif_ops_be, + .be_hw_params_fixup = be_hw_params_fixup, + SND_SOC_DAILINK_REG(hifi_be), + }, + +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"CPU-Capture", NULL, "Capture"}, + {"ASRC-Capture", NULL, "CPU-Capture"}, +}; + +static int imx_ak5558_probe(struct platform_device *pdev) +{ + struct imx_ak5558_data *priv; + struct device_node *cpu_np, *codec_np = NULL; + struct platform_device *cpu_pdev; + struct device_node *asrc_np = NULL; + struct platform_device *asrc_pdev = NULL; + int ret; + u32 width; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL)) + priv->tdm_mode = true; + + imx_ak5558_dai[0].codecs->of_node = codec_np; + imx_ak5558_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_ak5558_dai[0].platforms->of_node = cpu_np; + imx_ak5558_dai[0].capture_only = 1; + + priv->card.dai_link = &imx_ak5558_dai[0]; + priv->card.num_links = 1; + priv->card.dapm_routes = audio_map; + priv->card.num_dapm_routes = 1; + + /*if there is no asrc controller, we only enable one device*/ + if (asrc_pdev) { + imx_ak5558_dai[1].cpus->of_node = asrc_np; + imx_ak5558_dai[1].platforms->of_node = asrc_np; + + imx_ak5558_dai[2].codecs->of_node = codec_np; + imx_ak5558_dai[2].cpus->of_node = cpu_np; + priv->card.num_links = 3; + priv->card.num_dapm_routes += 1; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_ak5558_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak5558_dapm_widgets); + priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node, + "fsl,imx-audio-ak5558-mq"); + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_ak5558_dt_ids[] = { + { .compatible = "fsl,imx-audio-ak5552", }, + { .compatible = "fsl,imx-audio-ak5558", }, + { .compatible = "fsl,imx-audio-ak5558-mq", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ak5558_dt_ids); + +static struct platform_driver imx_ak5558_driver = { + .driver = { + .name = "imx-ak5558", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_ak5558_dt_ids, + }, + .probe = imx_ak5558_probe, +}; +module_platform_driver(imx_ak5558_driver); + +MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); +MODULE_DESCRIPTION("Freescale i.MX AK5558 ASoC machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ak5558"); diff --git a/sound/soc/fsl/imx-cdnhdmi.c b/sound/soc/fsl/imx-cdnhdmi.c new file mode 100644 index 000000000000..6b4ef2164bba --- /dev/null +++ b/sound/soc/fsl/imx-cdnhdmi.c @@ -0,0 +1,592 @@ +/* + * Copyright 2017-2018 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/hdmi-codec.h> +#include <drm/drm_connector.h> +#include "fsl_sai.h" + +#define SUPPORT_RATE_NUM 10 +#define SUPPORT_CHANNEL_NUM 10 + +struct imx_cdnhdmi_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + struct snd_soc_jack hdmi_jack; + struct snd_soc_jack_pin hdmi_jack_pin; + int protocol; + u32 support_rates[SUPPORT_RATE_NUM]; + u32 support_rates_num; + u32 support_channels[SUPPORT_CHANNEL_NUM]; + u32 support_channels_num; + u32 edid_rates[SUPPORT_RATE_NUM]; + u32 edid_rates_count; + u32 edid_channels[SUPPORT_CHANNEL_NUM]; + u32 edid_channels_count; + uint8_t eld[MAX_ELD_BYTES]; +}; + +static int imx_cdnhdmi_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + constraint_rates.list = data->support_rates; + constraint_rates.count = data->support_rates_num; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + constraint_channels.list = data->support_channels; + constraint_channels.count = data->support_channels_num; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret) + return ret; + + return 0; +} + +static int imx_cdnhdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret; + + /* set cpu DAI configuration */ + if (tx) + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + + if (of_device_is_compatible(dev->of_node, + "fsl,imx8mq-evk-cdnhdmi")) + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, + 256 * params_rate(params), + SND_SOC_CLOCK_OUT); + else + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, + 0, + tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 32); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_ops imx_cdnhdmi_ops = { + .startup = imx_cdnhdmi_startup, + .hw_params = imx_cdnhdmi_hw_params, +}; + +static const unsigned int eld_rates[] = { + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, +}; + +static unsigned int sad_max_channels(const u8 *sad) +{ + return 1 + (sad[0] & 7); +} + +static int get_edid_info(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = codec_dai->component; + struct hdmi_codec_pdata *hcd = component->dev->platform_data; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i, j, ret; + const u8 *sad; + unsigned int channel_max = 0; + unsigned int rate_mask = 0; + unsigned int rate_mask_eld = 0; + + ret = hcd->ops->get_eld(component->dev->parent, hcd->data, + data->eld, sizeof(data->eld)); + sad = drm_eld_sad(data->eld); + if (sad) { + for (j = 0; j < data->support_rates_num; j++) { + for (i = 0; i < ARRAY_SIZE(eld_rates); i++) + if (eld_rates[i] == data->support_rates[j]) + rate_mask |= BIT(i); + } + + for (i = drm_eld_sad_count(data->eld); i > 0; i--, sad += 3) { + if (rate_mask & sad[1]) + channel_max = max(channel_max, sad_max_channels(sad)); + + if (sad_max_channels(sad) >= 2) + rate_mask_eld |= sad[1]; + } + } + + rate_mask = rate_mask & rate_mask_eld; + + data->edid_rates_count = 0; + data->edid_channels_count = 0; + + for (i = 0; i < ARRAY_SIZE(eld_rates); i++) { + if (rate_mask & BIT(i)) { + data->edid_rates[data->edid_rates_count] = eld_rates[i]; + data->edid_rates_count++; + } + } + + for (i = 0; i < data->support_channels_num; i++) { + if (data->support_channels[i] <= channel_max) { + data->edid_channels[data->edid_channels_count] + = data->support_channels[i]; + data->edid_channels_count++; + } + } + + return 0; +} + +static int imx_cdnhdmi_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + + get_edid_info(card); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = data->edid_channels_count; + + return 0; +} + +static int imx_cdnhdmi_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i; + + get_edid_info(card); + + for (i = 0 ; i < data->edid_channels_count ; i++) + uvalue->value.integer.value[i] = data->edid_channels[i]; + + return 0; +} + +static int imx_cdnhdmi_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + + get_edid_info(card); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = data->edid_rates_count; + + return 0; +} + +static int imx_cdnhdmi_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i; + + get_edid_info(card); + + for (i = 0 ; i < data->edid_rates_count; i++) + uvalue->value.integer.value[i] = data->edid_rates[i]; + + return 0; +} + +static int imx_cdnhdmi_formats_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + + return 0; +} + +static int imx_cdnhdmi_formats_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = 16; + uvalue->value.integer.value[1] = 24; + uvalue->value.integer.value[2] = 32; + + return 0; +} + +static int get_edid_rx_info(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = codec_dai->component; + struct hdmi_codec_pdata *hcd = component->dev->platform_data; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = hcd->ops->get_eld(component->dev->parent, hcd->data, + data->eld, sizeof(data->eld)); + + if (ret) + return -EINVAL; + + data->edid_rates[0] = data->eld[0] + + (data->eld[1] << 8) + + (data->eld[2] << 16) + + (data->eld[3] << 24); + + data->edid_channels[0] = data->eld[4] + + (data->eld[5] << 8) + + (data->eld[6] << 16) + + (data->eld[7] << 24); + + + return 0; +} + +static int imx_cdnhdmi_rx_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 2; + uinfo->value.integer.max = 8; + + return 0; +} + +static int imx_cdnhdmi_rx_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = get_edid_rx_info(card); + if (ret) + return ret; + uvalue->value.integer.value[0] = data->edid_channels[0]; + + return 0; +} + +static int imx_cdnhdmi_rx_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 16000; + uinfo->value.integer.max = 192000; + + return 0; +} + +static int imx_cdnhdmi_rx_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = get_edid_rx_info(card); + if (ret) + return ret; + + uvalue->value.integer.value[0] = data->edid_rates[0]; + + return 0; +} + +static const struct snd_soc_dapm_widget imx_cdnhdmi_widgets[] = { + SND_SOC_DAPM_LINE("HDMI Jack", NULL), +}; + +static int imx_cdnhdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = codec_dai->component; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + data->hdmi_jack_pin.pin = "HDMI Jack"; + data->hdmi_jack_pin.mask = SND_JACK_LINEOUT; + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "HDMI Jack", SND_JACK_LINEOUT, + &data->hdmi_jack, &data->hdmi_jack_pin, 1); + if (ret) { + dev_err(card->dev, "Can't new HDMI Jack %d\n", ret); + return ret; + } + + return hdmi_codec_set_jack_detect(component, &data->hdmi_jack); +}; + +static struct snd_kcontrol_new imx_cdnhdmi_ctrls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_channels_info, + .get = imx_cdnhdmi_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rates_info, + .get = imx_cdnhdmi_rates_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Formats", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_formats_info, + .get = imx_cdnhdmi_formats_get, + }, +}; + +static struct snd_kcontrol_new imx_cdnhdmi_rx_ctrls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Rx Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rx_channels_info, + .get = imx_cdnhdmi_rx_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Rx Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rx_rates_info, + .get = imx_cdnhdmi_rx_rates_get, + }, +}; + +static int imx_cdnhdmi_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *cdnhdmi_np = NULL; + struct platform_device *cpu_pdev; + struct imx_cdnhdmi_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + int i; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + for (i = 0; i < SUPPORT_RATE_NUM; i++) { + ret = of_property_read_u32_index(pdev->dev.of_node, + "constraint-rate", + i, &data->support_rates[i]); + if (!ret) + data->support_rates_num = i + 1; + else + break; + } + + if (data->support_rates_num == 0) { + data->support_rates[0] = 48000; + data->support_rates[1] = 96000; + data->support_rates[2] = 32000; + data->support_rates[3] = 192000; + data->support_rates_num = 4; + } + + data->support_channels[0] = 2; + data->support_channels[1] = 4; + data->support_channels[2] = 6; + data->support_channels[3] = 8; + data->support_channels_num = 4; + + of_property_read_u32(pdev->dev.of_node, "protocol", + &data->protocol); + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "imx8 hdmi"; + data->dai.stream_name = "imx8 hdmi"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.ops = &imx_cdnhdmi_ops; + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + data->dai.init = imx_cdnhdmi_init; + + if (of_property_read_bool(pdev->dev.of_node, "hdmi-out")) { + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + data->dai.codecs->dai_name = "i2s-hifi"; + data->dai.codecs->name = "hdmi-audio-codec.1"; + data->card.controls = imx_cdnhdmi_ctrls; + data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_ctrls); + } + + if (of_property_read_bool(pdev->dev.of_node, "hdmi-in")) { + data->dai.playback_only = false; + data->dai.capture_only = true; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + data->dai.codecs->dai_name = "i2s-hifi"; + data->dai.codecs->name = "hdmi-audio-codec.2"; + data->card.controls = imx_cdnhdmi_rx_ctrls; + data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_rx_ctrls); + } + + if ((data->dai.playback_only && data->dai.capture_only) + || (!data->dai.playback_only && !data->dai.capture_only)) { + dev_err(&pdev->dev, "Wrongly enable HDMI DAI link\n"); + goto fail; + } + + data->card.dapm_widgets = imx_cdnhdmi_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_cdnhdmi_widgets); + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (cdnhdmi_np) + of_node_put(cdnhdmi_np); + return ret; +} + +static const struct of_device_id imx_cdnhdmi_dt_ids[] = { + { .compatible = "fsl,imx8mq-evk-cdnhdmi", }, + { .compatible = "fsl,imx-audio-cdnhdmi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_cdnhdmi_dt_ids); + +static struct platform_driver imx_cdnhdmi_driver = { + .driver = { + .name = "imx-cdnhdmi", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_cdnhdmi_dt_ids, + }, + .probe = imx_cdnhdmi_probe, +}; +module_platform_driver(imx_cdnhdmi_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX hdmi audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-cdnhdmi"); diff --git a/sound/soc/fsl/imx-cs42888.c b/sound/soc/fsl/imx-cs42888.c new file mode 100644 index 000000000000..9520b0891ddf --- /dev/null +++ b/sound/soc/fsl/imx-cs42888.c @@ -0,0 +1,631 @@ +/* + * Copyright (C) 2010-2016 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> + +#include "fsl_esai.h" + +#define CODEC_CLK_EXTER_OSC 1 +#define CODEC_CLK_ESAI_HCKT 2 +#define SUPPORT_RATE_NUM 10 + +struct imx_priv { + struct clk *codec_clk; + struct clk *esai_clk; + unsigned int mclk_freq; + unsigned int esai_freq; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; + bool is_codec_master; + bool is_codec_rpmsg; + bool is_stream_in_use[2]; + bool is_stream_tdm[2]; +}; + +static struct imx_priv card_priv; + +static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 channels = params_channels(params); + u32 max_tdm_rate; + u32 dai_format; + int ret = 0; + + priv->is_stream_tdm[tx] = channels > 1 && channels % 2; + dai_format = SND_SOC_DAIFMT_NB_NF | + (priv->is_stream_tdm[tx] ? SND_SOC_DAIFMT_DSP_A : + SND_SOC_DAIFMT_LEFT_J); + + priv->is_stream_in_use[tx] = true; + + if (priv->is_stream_in_use[!tx] && + (priv->is_stream_tdm[tx] != priv->is_stream_tdm[!tx])) { + + dev_err(dev, "Don't support different fmt for tx & rx\n"); + return -EINVAL; + } + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + priv->esai_freq = clk_get_rate(priv->esai_clk); + + if (priv->is_codec_master) { + /* TDM is not supported by codec in master mode */ + if (priv->is_stream_tdm[tx]) { + dev_err(dev, "%d channels are not supported in codec master mode\n", + channels); + return -EINVAL; + } + dai_format |= SND_SOC_DAIFMT_CBM_CFM; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_IN); + else + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + + } else { + dai_format |= SND_SOC_DAIFMT_CBS_CFS; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + else + ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL, + priv->mclk_freq, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set i.MX active slot mask */ + if (priv->is_stream_tdm[tx]) { + /* 2 required by ESAI BCLK divisors, 8 slots, 32 width */ + if (priv->is_codec_master) + max_tdm_rate = priv->mclk_freq / (8*32); + else + max_tdm_rate = priv->esai_freq / (2*8*32); + if (params_rate(params) > max_tdm_rate) { + dev_err(dev, + "maximum supported sampling rate for %d channels is %dKHz\n", + channels, max_tdm_rate / 1000); + return -EINVAL; + } + + /* + * Per datasheet, the codec expects 8 slots and 32 bits + * for every slot in TDM mode. + */ + snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + 8, 32); + } else + snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32); + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + return 0; +} + +static int imx_cs42888_surround_hw_free(struct snd_pcm_substream *substream) +{ + struct imx_priv *priv = &card_priv; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + priv->is_stream_in_use[tx] = false; + + return 0; +} + +static int imx_cs42888_surround_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + static u32 support_rates[SUPPORT_RATE_NUM]; + int ret; + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + + if (priv->mclk_freq % 12288000 == 0) { + support_rates[0] = 48000; + support_rates[1] = 96000; + support_rates[2] = 192000; + constraint_rates.list = support_rates; + constraint_rates.count = 3; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + } else + dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq); + + return 0; +} + +static struct snd_soc_ops imx_cs42888_surround_ops = { + .startup = imx_cs42888_surround_startup, + .hw_params = imx_cs42888_surround_hw_params, + .hw_free = imx_cs42888_surround_hw_free, +}; + +/** + * imx_cs42888_surround_startup() is to set constrain for hw parameter, but + * backend use same runtime as frontend, for p2p backend need to use different + * parameter, so backend can't use the startup. + */ +static struct snd_soc_ops imx_cs42888_surround_ops_be = { + .hw_params = imx_cs42888_surround_hw_params, + .hw_free = imx_cs42888_surround_hw_free, +}; + + +static const struct snd_soc_dapm_widget imx_cs42888_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Line out jack */ + {"Line Out Jack", NULL, "AOUT1L"}, + {"Line Out Jack", NULL, "AOUT1R"}, + {"Line Out Jack", NULL, "AOUT2L"}, + {"Line Out Jack", NULL, "AOUT2R"}, + {"Line Out Jack", NULL, "AOUT3L"}, + {"Line Out Jack", NULL, "AOUT3R"}, + {"Line Out Jack", NULL, "AOUT4L"}, + {"Line Out Jack", NULL, "AOUT4R"}, + {"AIN1L", NULL, "Line In Jack"}, + {"AIN1R", NULL, "Line In Jack"}, + {"AIN2L", NULL, "Line In Jack"}, + {"AIN2R", NULL, "Line In Jack"}, + {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */ + {"CPU-Capture", NULL, "Capture"}, + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"ASRC-Capture", NULL, "CPU-Capture"}, +}; + +static const struct snd_soc_dapm_route audio_map_v2[] = { + /* Line out jack */ + {"Line Out Jack", NULL, "AOUT1L"}, + {"Line Out Jack", NULL, "AOUT1R"}, + {"Line Out Jack", NULL, "AOUT2L"}, + {"Line Out Jack", NULL, "AOUT2R"}, + {"Line Out Jack", NULL, "AOUT3L"}, + {"Line Out Jack", NULL, "AOUT3R"}, + {"Line Out Jack", NULL, "AOUT4L"}, + {"Line Out Jack", NULL, "AOUT4R"}, + {"AIN1L", NULL, "Line In Jack"}, + {"AIN1R", NULL, "Line In Jack"}, + {"AIN2L", NULL, "Line In Jack"}, + {"AIN2R", NULL, "Line In Jack"}, + {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */ + {"CPU-Capture", NULL, "Capture"}, + {"CPU-Playback", NULL, "CLIENT0-Playback"}, + {"CLIENT0-Capture", NULL, "CPU-Capture"}, + {"CPU-Playback", NULL, "CLIENT1-Playback"}, + {"CLIENT1-Capture", NULL, "CPU-Capture"}, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +static int be_hw_params_fixup_v2(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_substream *substream = snd_soc_dpcm_get_substream(rtd, SNDRV_PCM_STREAM_PLAYBACK); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_esai_mix *mix = &esai->mix[tx]; + struct snd_interval *channels; + + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + channels->max = channels->min = mix->channels; + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42888")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe2, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42888")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link imx_cs42888_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .ops = &imx_cs42888_surround_ops, + .ignore_pmdown_time = 1, + SND_SOC_DAILINK_REG(hifi), + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &imx_cs42888_surround_ops_be, + .be_hw_params_fixup = be_hw_params_fixup, + SND_SOC_DAILINK_REG(hifi_be), + }, +}; + +static struct snd_soc_dai_link imx_cs42888_dai_v2[] = { + { + .name = "HiFi-ASRC-FE1", + .stream_name = "HiFi-ASRC-FE1", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + .dpcm_merged_rate = 1, + .dpcm_merged_format = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-FE2", + .stream_name = "HiFi-ASRC-FE2", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + .dpcm_merged_rate = 1, + .dpcm_merged_format = 1, + SND_SOC_DAILINK_REG(hifi_fe2), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &imx_cs42888_surround_ops_be, + .be_hw_params_fixup = be_hw_params_fixup_v2, + SND_SOC_DAILINK_REG(hifi_be), + }, +}; + +static struct snd_soc_card snd_soc_card_imx_cs42888 = { + .name = "cs42888-audio", + .dai_link = imx_cs42888_dai, + .dapm_widgets = imx_cs42888_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(imx_cs42888_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .owner = THIS_MODULE, +}; + +/* + * This function will register the snd_soc_pcm_link drivers. + */ +static int imx_cs42888_probe(struct platform_device *pdev) +{ + struct device_node *esai_np, *codec_np; + struct device_node *asrc_np = NULL; + struct platform_device *esai_pdev; + struct platform_device *asrc_pdev = NULL; + struct imx_priv *priv = &card_priv; + struct of_phandle_args args[2]; + struct platform_device *cpu_pdev[2]; + int ret; + u32 width; + + priv->pdev = pdev; + priv->asrc_pdev = NULL; + + if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg")) + priv->is_codec_rpmsg = true; + + esai_np = of_parse_phandle(pdev->dev.of_node, "esai-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!esai_np || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + esai_pdev = of_find_device_by_node(esai_np); + if (!esai_pdev) { + dev_err(&pdev->dev, "failed to find ESAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + if (priv->is_codec_rpmsg) { + struct platform_device *codec_dev; + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + } else { + struct i2c_client *codec_dev; + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_dbg(&pdev->dev, "failed to find codec platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } + + if (priv->is_codec_rpmsg) { + imx_cs42888_dai[0].codecs->name = "rpmsg-audio-codec-cs42888"; + imx_cs42888_dai[0].codecs->dai_name = "cs42888"; + } else { + imx_cs42888_dai[0].codecs->of_node = codec_np; + } + + /*if there is no asrc controller, we only enable one device*/ + if (!asrc_pdev) { + imx_cs42888_dai[0].cpus->dai_name = dev_name(&esai_pdev->dev); + imx_cs42888_dai[0].platforms->of_node = esai_np; + snd_soc_card_imx_cs42888.num_links = 1; + snd_soc_card_imx_cs42888.num_dapm_routes = + ARRAY_SIZE(audio_map) - 2; + } else { + imx_cs42888_dai[0].cpus->dai_name = dev_name(&esai_pdev->dev); + imx_cs42888_dai[0].platforms->of_node = esai_np; + imx_cs42888_dai[1].cpus->of_node = asrc_np; + imx_cs42888_dai[1].platforms->of_node = asrc_np; + imx_cs42888_dai[2].cpus->dai_name = dev_name(&esai_pdev->dev); + snd_soc_card_imx_cs42888.num_links = 3; + + if (priv->is_codec_rpmsg) { + imx_cs42888_dai[2].codecs->name = "rpmsg-audio-codec-cs42888"; + imx_cs42888_dai[2].codecs->dai_name = "cs42888"; + } else { + imx_cs42888_dai[2].codecs->of_node = codec_np; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + /* switch to v2 if there is "client-dais" property */ + if (of_property_read_bool(esai_np, "client-dais")) { + ret = of_parse_phandle_with_args(esai_np, "client-dais", NULL, 0, + &args[0]); + if (ret < 0) { + dev_err(&pdev->dev, "of_parse_phandle_with_args failed\n"); + return ret; + } + + cpu_pdev[0] = of_find_device_by_node(args[0].np); + if (!cpu_pdev[0]) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + return -EINVAL; + } + + ret = of_parse_phandle_with_args(esai_np, "client-dais", NULL, 1, + &args[1]); + if (ret < 0) { + dev_err(&pdev->dev, "of_parse_phandle_with_args failed\n"); + return ret; + } + + cpu_pdev[1] = of_find_device_by_node(args[1].np); + if (!cpu_pdev[1]) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + return -EINVAL; + } + + imx_cs42888_dai_v2[0].cpus->dai_name = dev_name(&cpu_pdev[0]->dev); + imx_cs42888_dai_v2[0].cpus->of_node = args[0].np; + imx_cs42888_dai_v2[0].platforms->of_node = args[0].np; + imx_cs42888_dai_v2[1].cpus->dai_name = dev_name(&cpu_pdev[1]->dev); + imx_cs42888_dai_v2[1].cpus->of_node = args[1].np; + imx_cs42888_dai_v2[1].platforms->of_node = args[1].np; + imx_cs42888_dai_v2[2].cpus->dai_name = dev_name(&esai_pdev->dev); + imx_cs42888_dai_v2[2].codecs->of_node = codec_np; + + snd_soc_card_imx_cs42888.dai_link = imx_cs42888_dai_v2; + snd_soc_card_imx_cs42888.num_links = 3; + snd_soc_card_imx_cs42888.dapm_routes = audio_map_v2, + snd_soc_card_imx_cs42888.num_dapm_routes = ARRAY_SIZE(audio_map_v2); + } + + priv->esai_clk = devm_clk_get(&esai_pdev->dev, "extal"); + if (IS_ERR(priv->esai_clk)) { + ret = PTR_ERR(priv->esai_clk); + dev_err(&esai_pdev->dev, "failed to get cpu clk: %d\n", ret); + goto fail; + } + + priv->is_codec_master = false; + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + priv->is_codec_master = true; + + snd_soc_card_imx_cs42888.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&snd_soc_card_imx_cs42888, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &snd_soc_card_imx_cs42888); + + ret = snd_soc_register_card(&snd_soc_card_imx_cs42888); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); +fail: + if (asrc_np) + of_node_put(asrc_np); + if (esai_np) + of_node_put(esai_np); + if (codec_np) + of_node_put(codec_np); + return ret; +} + +static int imx_cs42888_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&snd_soc_card_imx_cs42888); + return 0; +} + +static const struct of_device_id imx_cs42888_dt_ids[] = { + { .compatible = "fsl,imx-audio-cs42888", }, + { /* sentinel */ } +}; + +static struct platform_driver imx_cs42888_driver = { + .probe = imx_cs42888_probe, + .remove = imx_cs42888_remove, + .driver = { + .name = "imx-cs42888", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_cs42888_dt_ids, + }, +}; +module_platform_driver(imx_cs42888_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver"); +MODULE_ALIAS("platform:imx-cs42888"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-dsp.c b/sound/soc/fsl/imx-dsp.c new file mode 100644 index 000000000000..15ffd3ba20c4 --- /dev/null +++ b/sound/soc/fsl/imx-dsp.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: (GPL-2.0+ +// +// DSP machine driver +// +// Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. +// Copyright 2018 NXP + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/string.h> + +struct imx_dsp_audio_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; +}; + +static int imx_dsp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + clk_get_rate(devm_clk_get(codec_dai->dev, "mclk")), SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + return 0; +} + +static struct snd_soc_ops imx_dsp_ops_be = { + .hw_params = imx_dsp_hw_params, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct snd_interval *rate; + struct snd_interval *channels; + struct snd_mask *mask; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = 48000; + + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + channels->max = channels->min = 2; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static const struct snd_soc_dapm_route imx_dsp_audio_map[] = { + {"Playback", NULL, "Compress Playback"},/* dai route for be and fe */ +}; + +static int imx_dsp_audio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *cpu_np=NULL, *codec_np=NULL, *platform_np=NULL; + struct snd_soc_dai_link_component *comp; + struct platform_device *cpu_pdev; + struct imx_dsp_audio_data *data; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + comp = devm_kzalloc(&pdev->dev, 6 * sizeof(*comp), GFP_KERNEL); + if (!comp) { + ret = -ENOMEM; + goto fail; + } + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find rpmsg platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + platform_np = of_parse_phandle(pdev->dev.of_node, "audio-platform", 0); + if (!platform_np) { + dev_err(&pdev->dev, "platform missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + + data->dai[0].cpus = &comp[0]; + data->dai[0].codecs = &comp[1]; + data->dai[0].platforms = &comp[2]; + + data->dai[0].num_cpus = 1; + data->dai[0].num_codecs = 1; + data->dai[0].num_platforms = 1; + + data->dai[0].name = "dsp hifi fe"; + data->dai[0].stream_name = "dsp hifi fe"; + data->dai[0].codecs->dai_name = "snd-soc-dummy-dai"; + data->dai[0].codecs->name = "snd-soc-dummy"; + data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].cpus->of_node = cpu_np; + data->dai[0].platforms->of_node = platform_np; + data->dai[0].playback_only = true; + data->dai[0].capture_only = false; + data->dai[0].dpcm_playback = 1; + data->dai[0].dpcm_capture = 0; + data->dai[0].dynamic = 1, + data->dai[0].ignore_pmdown_time = 1, + data->dai[0].dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + data->dai[1].cpus = &comp[3]; + data->dai[1].codecs = &comp[4]; + data->dai[1].platforms = &comp[5]; + + data->dai[1].num_cpus = 1; + data->dai[1].num_codecs = 1; + data->dai[1].num_platforms = 1; + + data->dai[1].name = "dsp hifi be"; + data->dai[1].stream_name = "dsp hifi be"; + if (sysfs_streq(codec_np->name, "wm8960")) { + if (of_device_is_compatible(np, "fsl,imx-dsp-audio-lpa")) + data->dai[1].codecs->dai_name = "rpmsg-wm8960-hifi"; + else + data->dai[1].codecs->dai_name = "wm8960-hifi"; + data->dai[1].dai_fmt = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBS_CFS; + } else { + data->dai[1].codecs->dai_name = "cs42888"; + data->dai[1].dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + } + data->dai[1].codecs->of_node = codec_np; + data->dai[1].cpus->dai_name = "snd-soc-dummy-dai"; + data->dai[1].cpus->name = "snd-soc-dummy"; + data->dai[1].platforms->name = "snd-soc-dummy"; + data->dai[1].playback_only = true; + data->dai[1].capture_only = false; + data->dai[1].dpcm_playback = 1; + if (of_device_is_compatible(np, "fsl,imx-dsp-audio-lpa")) + data->dai[1].ignore_suspend = 1; + data->dai[1].dpcm_capture = 0; + data->dai[1].no_pcm = 1, + data->dai[1].ignore_pmdown_time = 1, + data->dai[1].ops = &imx_dsp_ops_be; + data->dai[1].be_hw_params_fixup = be_hw_params_fixup; + + data->card.dapm_routes = imx_dsp_audio_map; + data->card.num_dapm_routes = ARRAY_SIZE(imx_dsp_audio_map); + data->card.num_links = 2; + data->card.dai_link = data->dai; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + if (platform_np) + of_node_put(platform_np); + return ret; +} + +static const struct of_device_id imx_dsp_audio_dt_ids[] = { + { .compatible = "fsl,imx-dsp-audio", }, + { .compatible = "fsl,imx-dsp-audio-lpa", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_dsp_audio_dt_ids); + +static struct platform_driver imx_dsp_audio_driver = { + .driver = { + .name = "imx-dsp-audio", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_dsp_audio_dt_ids, + }, + .probe = imx_dsp_audio_probe, +}; +module_platform_driver(imx_dsp_audio_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-hdmi-dma.c b/sound/soc/fsl/imx-hdmi-dma.c new file mode 100644 index 000000000000..95402a23560a --- /dev/null +++ b/sound/soc/fsl/imx-hdmi-dma.c @@ -0,0 +1,1193 @@ +/* + * imx-hdmi-dma.c -- HDMI DMA driver for ALSA Soc Audio Layer + * + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. + * + * based on imx-pcm-dma-mx2.c + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <linux/platform_data/dma-imx.h> + +#include <video/mxc_hdmi.h> + +#include "imx-hdmi.h" + +#define DRV_NAME "imx_hdmi_dma" + +#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH 0 +#define HDMI_DMA_BURST_INCR4 1 +#define HDMI_DMA_BURST_INCR8 2 +#define HDMI_DMA_BURST_INCR16 3 + +#define HDMI_BASE_ADDR 0x00120000 + +struct hdmi_sdma_script { + int control_reg_addr; + int status_reg_addr; + int dma_start_addr; + u32 buffer[20]; +}; + +struct hdmi_dma_priv { + struct snd_pcm_substream *substream; + struct platform_device *pdev; + + struct snd_dma_buffer hw_buffer; + unsigned long buffer_bytes; + unsigned long appl_bytes; + + int periods; + int period_time; + int period_bytes; + int dma_period_bytes; + int buffer_ratio; + + unsigned long offset; + + snd_pcm_format_t format; + int sample_align; + int sample_bits; + int channels; + int rate; + + int frame_idx; + + bool tx_active; + spinlock_t irq_lock; + + /* SDMA part */ + dma_addr_t phy_hdmi_sdma_t; + struct hdmi_sdma_script *hdmi_sdma_t; + struct dma_chan *dma_channel; + struct dma_async_tx_descriptor *desc; + struct imx_hdmi_sdma_params sdma_params; +}; + +/* bit 0:0:0:b:p(0):c:(u)0:(v)0 */ +/* max 8 channels supported; channels are interleaved */ +static u8 g_packet_head_table[48 * 8]; + +void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst, + int samples, unsigned char *lookup_table); +void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst, + int samples); +void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst, + int samples, unsigned char *lookup_table); +void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst, + int samples); +static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv); +static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv); + +union hdmi_audio_header_t iec_header; +EXPORT_SYMBOL(iec_header); + +/* + * Note that the period size for DMA != period size for ALSA because the + * driver adds iec frame info to the audio samples (in hdmi_dma_copy). + * + * Each 4 byte subframe = 1 byte of iec data + 3 byte audio sample. + * + * A 16 bit audio sample becomes 32 bits including the frame info. Ratio=2 + * A 24 bit audio sample becomes 32 bits including the frame info. Ratio=3:4 + * If the 24 bit raw audio is in 32 bit words, the + * + * Original Packed into subframe Ratio of size Format + * sample how many size of DMA buffer + * (bits) bits to ALSA buffer + * -------- ----------- -------- -------------- ------------------------ + * 16 16 32 2 SNDRV_PCM_FORMAT_S16_LE + * 24 24 32 1.33 SNDRV_PCM_FORMAT_S24_3LE* + * 24 32 32 1 SNDRV_PCM_FORMAT_S24_LE + * + * *so SNDRV_PCM_FORMAT_S24_3LE is not supported. + */ + +/* + * The minimum dma period is one IEC audio frame (192 * 4 * channels). + * The maximum dma period for the HDMI DMA is 8K. + * + * channels minimum maximum + * dma period dma period + * -------- ------------------ ---------- + * 2 192 * 4 * 2 = 1536 * 4 = 6144 + * 4 192 * 4 * 4 = 3072 * 2 = 6144 + * 6 192 * 4 * 6 = 4608 * 1 = 4608 + * 8 192 * 4 * 8 = 6144 * 1 = 6144 + * + * Bottom line: + * 1. Must keep the ratio of DMA buffer to ALSA buffer consistent. + * 2. frame_idx is saved in the private data, so even if a frame cannot be + * transmitted in a period, it can be continued in the next period. This + * is necessary for 6 ch. + */ +#define HDMI_DMA_PERIOD_BYTES (12288) +#define HDMI_DMA_BUF_SIZE (128 * 1024) +#define HDMI_PCM_BUF_SIZE (128 * 1024) + +#define hdmi_audio_debug(dev, reg) \ + dev_dbg(dev, #reg ": 0x%02x\n", hdmi_readb(reg)) + +#ifdef DEBUG +static void dumpregs(struct device *dev) +{ + hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_START); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STOP); + hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STRADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STPADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BSTADDR0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH0); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH1); + hdmi_audio_debug(dev, HDMI_AHB_DMA_STAT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_INT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_MASK); + hdmi_audio_debug(dev, HDMI_AHB_DMA_POL); + hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF1); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFSTAT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFINT); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFMASK); + hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFPOL); + hdmi_audio_debug(dev, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_audio_debug(dev, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_audio_debug(dev, HDMI_IH_MUTE); +} + +static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) +{ + dev_dbg(dev, "channels = %d\n", priv->channels); + dev_dbg(dev, "periods = %d\n", priv->periods); + dev_dbg(dev, "period_bytes = %d\n", priv->period_bytes); + dev_dbg(dev, "dma period_bytes = %d\n", priv->dma_period_bytes); + dev_dbg(dev, "buffer_ratio = %d\n", priv->buffer_ratio); + dev_dbg(dev, "hw dma buffer = 0x%08x\n", (int)priv->hw_buffer.addr); + dev_dbg(dev, "dma buf size = %d\n", (int)priv->buffer_bytes); + dev_dbg(dev, "sample_rate = %d\n", (int)priv->rate); +} +#else +static void dumpregs(struct device *dev) {} +static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) {} +#endif + +/* + * Conditions for DMA to work: + * ((final_addr - initial_addr)>>2)+1) < 2k. So max period is 8k. + * (inital_addr & 0x3) == 0 + * (final_addr & 0x3) == 0x3 + * + * The DMA Period should be an integer multiple of the IEC 60958 audio + * frame size, which is 768 bytes (192 * 4). + */ +static void hdmi_dma_set_addr(int start_addr, int dma_period_bytes) +{ + int final_addr = start_addr + dma_period_bytes - 1; + + hdmi_write4(start_addr, HDMI_AHB_DMA_STRADDR0); + hdmi_write4(final_addr, HDMI_AHB_DMA_STPADDR0); +} + +static void hdmi_dma_irq_set(bool set) +{ + u8 val = hdmi_readb(HDMI_AHB_DMA_MASK); + + if (set) + val |= HDMI_AHB_DMA_DONE; + else + val &= (u8)~HDMI_AHB_DMA_DONE; + + hdmi_writeb(val, HDMI_AHB_DMA_MASK); +} + +static void hdmi_mask(int mask) +{ + u8 regval = hdmi_readb(HDMI_AHB_DMA_MASK); + + if (mask) + regval |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY; + else + regval &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY); + + hdmi_writeb(regval, HDMI_AHB_DMA_MASK); +} + +int odd_ones(unsigned a) +{ + a ^= a >> 8; + a ^= a >> 4; + a ^= a >> 2; + a ^= a >> 1; + + return a & 1; +} + +/* Add frame information for one pcm subframe */ +static u32 hdmi_dma_add_frame_info(struct hdmi_dma_priv *priv, + u32 pcm_data, int subframe_idx) +{ + union hdmi_audio_dma_data_t subframe; + + subframe.U = 0; + iec_header.B.channel = subframe_idx; + + /* fill b (start-of-block) */ + subframe.B.b = (priv->frame_idx == 0) ? 1 : 0; + + /* fill c (channel status) */ + if (priv->frame_idx < 42) + subframe.B.c = (iec_header.U >> priv->frame_idx) & 0x1; + else + subframe.B.c = 0; + + subframe.B.p = odd_ones(pcm_data); + subframe.B.p ^= subframe.B.c; + subframe.B.p ^= subframe.B.u; + subframe.B.p ^= subframe.B.v; + + /* fill data */ + if (priv->sample_bits == 16) + subframe.B.data = pcm_data << 8; + else + subframe.B.data = pcm_data; + + return subframe.U; +} + +static void init_table(int channels) +{ + unsigned char *p = g_packet_head_table; + int i, ch = 0; + + for (i = 0; i < 48; i++) { + int b = 0; + if (i == 0) + b = 1; + + for (ch = 0; ch < channels; ch++) { + int c = 0; + if (i < 42) { + iec_header.B.channel = ch+1; + c = (iec_header.U >> i) & 0x1; + } + /* preset bit p as c */ + *p++ = (b << 4) | (c << 2) | (c << 3); + } + } +} + +/* + * FIXME: Disable NEON Optimization in hdmi, or it will cause crash of + * pulseaudio in the userspace. There is no issue for the Optimization + * implemenation, if only use "VPUSH, VPOP" in the function, the pulseaudio + * will crash also. So for my assumption, we can't use the NEON in the + * interrupt.(tasklet is implemented by softirq.) + * Disable SMP, preempt, change the dma buffer to nocached, add protection of + * Dn register and fpscr, all these operation have no effect to the result. + */ +#define HDMI_DMA_NO_NEON + +#ifdef HDMI_DMA_NO_NEON +/* Optimization for IEC head */ +static void hdmi_dma_copy_16_c_lut(u16 *src, u32 *dst, int samples, + u8 *lookup_table) +{ + u32 sample, head, p; + int i; + + for (i = 0; i < samples; i++) { + /* get source sample */ + sample = *src++; + + /* xor every bit */ + p = sample ^ (sample >> 8); + p ^= (p >> 4); + p ^= (p >> 2); + p ^= (p >> 1); + p &= 1; /* only want last bit */ + p <<= 3; /* bit p */ + + /* get packet header */ + head = *lookup_table++; + + /* fix head */ + head ^= p; + + /* store */ + *dst++ = (head << 24) | (sample << 8); + } +} + +static void hdmi_dma_copy_16_c_fast(u16 *src, u32 *dst, int samples) +{ + u32 sample, p; + int i; + + for (i = 0; i < samples; i++) { + /* get source sample */ + sample = *src++; + + /* xor every bit */ + p = sample ^ (sample >> 8); + p ^= (p >> 4); + p ^= (p >> 2); + p ^= (p >> 1); + p &= 1; /* only want last bit */ + p <<= 3; /* bit p */ + + /* store */ + *dst++ = (p << 24) | (sample << 8); + } +} + +static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecnt, int channelcnt) +{ + /* split input frames into 192-frame each */ + int count_in_192 = (framecnt + 191) / 192; + int i; + + for (i = 0; i < count_in_192; i++) { + int count, samples; + + /* handles frame index [0, 48) */ + count = (framecnt < 48) ? framecnt : 48; + samples = count * channelcnt; + hdmi_dma_copy_16_c_lut(src, dst, samples, g_packet_head_table); + framecnt -= count; + if (framecnt == 0) + break; + + src += samples; + dst += samples; + + /* handles frame index [48, 192) */ + count = (framecnt < 192 - 48) ? framecnt : 192 - 48; + samples = count * channelcnt; + hdmi_dma_copy_16_c_fast(src, dst, samples); + framecnt -= count; + src += samples; + dst += samples; + } +} +#else +/* NEON optimization for IEC head*/ + +/** + * Convert pcm samples to iec samples suitable for HDMI transfer. + * PCM sample is 16 bits length. + * Frame index always starts from 0. + * Channel count can be 1, 2, 4, 6, or 8 + * Sample count (frame_count * channel_count) is multipliable by 8. + */ +static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecount, int channelcount) +{ + /* split input frames into 192-frame each */ + int i, count_in_192 = (framecount + 191) / 192; + + for (i = 0; i < count_in_192; i++) { + int count, samples; + + /* handles frame index [0, 48) */ + count = (framecount < 48) ? framecount : 48; + samples = count * channelcount; + hdmi_dma_copy_16_neon_lut(src, dst, samples, g_packet_head_table); + framecount -= count; + if (framecount == 0) + break; + + src += samples; + dst += samples; + + /* handles frame index [48, 192) */ + count = (framecount < 192 - 48) ? framecount : (192 - 48); + samples = count * channelcount; + hdmi_dma_copy_16_neon_fast(src, dst, samples); + framecount -= count; + src += samples; + dst += samples; + } +} +#endif + +static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream, + int offset, int count) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = component->dev; + u32 framecount, *dst; + u16 *src16; + + framecount = count / (priv->sample_align * priv->channels); + + /* hw_buffer is the destination for pcm data plus frame info. */ + dst = (u32 *)(priv->hw_buffer.area + (offset * priv->buffer_ratio)); + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* dma_buffer is the mmapped buffer we are copying pcm from. */ + src16 = (u16 *)(runtime->dma_area + offset); + hdmi_dma_copy_16(src16, dst, framecount, priv->channels); + break; + default: + dev_err(dev, "unsupported sample format %s\n", + snd_pcm_format_name(priv->format)); + return; + } +} + +static void hdmi_dma_data_copy(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv, char type) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long offset, count, appl_bytes, space_to_end; + + if (runtime->access != SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) + return; + + appl_bytes = (runtime->status->hw_ptr % (priv->buffer_bytes/(runtime->frame_bits/8))) * (runtime->frame_bits/8); + if (type == 'p') + appl_bytes += 2 * priv->period_bytes; + offset = appl_bytes % priv->buffer_bytes; + + switch (type) { + case 'p': + count = priv->period_bytes; + space_to_end = priv->period_bytes; + break; + case 'b': + count = priv->buffer_bytes; + space_to_end = priv->buffer_bytes - offset; + + break; + default: + return; + } + + if (count <= space_to_end) { + hdmi_dma_mmap_copy(substream, offset, count); + } else { + hdmi_dma_mmap_copy(substream, offset, space_to_end); + hdmi_dma_mmap_copy(substream, 0, count - space_to_end); + } +} + +static void hdmi_sdma_callback(void *data) +{ + struct hdmi_dma_priv *priv = (struct hdmi_dma_priv *)data; + struct snd_pcm_substream *substream = priv->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + + if (runtime && runtime->dma_area && priv->tx_active) { + priv->offset += priv->period_bytes; + priv->offset %= priv->period_bytes * priv->periods; + + /* Copy data by period_bytes */ + hdmi_dma_data_copy(substream, priv, 'p'); + + snd_pcm_period_elapsed(substream); + } + + spin_unlock_irqrestore(&priv->irq_lock, flags); + + return; +} + +static int hdmi_dma_set_thrsld_incrtype(struct device *dev, int channels) +{ + u8 mask = HDMI_AHB_DMA_CONF0_BURST_MODE | HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK; + u8 val = hdmi_readb(HDMI_AHB_DMA_CONF0) & ~mask; + int incr_type, threshold; + + switch (hdmi_readb(HDMI_REVISION_ID)) { + case 0x0a: + incr_type = HDMI_DMA_BURST_INCR4; + if (channels == 2) + threshold = 126; + else + threshold = 124; + break; + case 0x1a: + incr_type = HDMI_DMA_BURST_INCR8; + threshold = 128; + break; + default: + dev_err(dev, "unknown hdmi controller!\n"); + return -ENODEV; + } + + hdmi_writeb(threshold, HDMI_AHB_DMA_THRSLD); + + switch (incr_type) { + case HDMI_DMA_BURST_UNSPECIFIED_LEGNTH: + break; + case HDMI_DMA_BURST_INCR4: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE; + break; + case HDMI_DMA_BURST_INCR8: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR8; + break; + case HDMI_DMA_BURST_INCR16: + val |= HDMI_AHB_DMA_CONF0_BURST_MODE | + HDMI_AHB_DMA_CONF0_INCR16; + break; + default: + dev_err(dev, "invalid increment type: %d!", incr_type); + return -EINVAL; + } + + hdmi_writeb(val, HDMI_AHB_DMA_CONF0); + + hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD); + + return 0; +} + +static int hdmi_dma_configure_dma(struct device *dev, int channels) +{ + u8 i, val = 0; + int ret; + + if (channels <= 0 || channels > 8 || channels % 2 != 0) { + dev_err(dev, "unsupported channel number: %d\n", channels); + return -EINVAL; + } + + hdmi_audio_writeb(AHB_DMA_CONF0, EN_HLOCK, 0x1); + + ret = hdmi_dma_set_thrsld_incrtype(dev, channels); + if (ret) + return ret; + + for (i = 0; i < channels; i += 2) + val |= 0x3 << i; + + hdmi_writeb(val, HDMI_AHB_DMA_CONF1); + + return 0; +} + +static void hdmi_dma_init_iec_header(void) +{ + iec_header.U = 0; + + iec_header.B.consumer = 0; /* Consumer use */ + iec_header.B.linear_pcm = 0; /* linear pcm audio */ + iec_header.B.copyright = 1; /* no copyright */ + iec_header.B.pre_emphasis = 0; /* 2 channels without pre-emphasis */ + iec_header.B.mode = 0; /* Mode 0 */ + + iec_header.B.category_code = 0; + + iec_header.B.source = 2; /* stereo */ + iec_header.B.channel = 0; + + iec_header.B.sample_freq = 0x02; /* 48 KHz */ + iec_header.B.clock_acc = 0; /* Level II */ + + iec_header.B.word_length = 0x02; /* 16 bits */ + iec_header.B.org_sample_freq = 0x0D; /* 48 KHz */ + + iec_header.B.cgms_a = 0; /* Copying is permitted without restriction */ +} + +static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = component->dev; + + iec_header.B.source = priv->channels; + + switch (priv->rate) { + case 32000: + iec_header.B.sample_freq = 0x03; + iec_header.B.org_sample_freq = 0x0C; + break; + case 44100: + iec_header.B.sample_freq = 0x00; + iec_header.B.org_sample_freq = 0x0F; + break; + case 48000: + iec_header.B.sample_freq = 0x02; + iec_header.B.org_sample_freq = 0x0D; + break; + case 88200: + iec_header.B.sample_freq = 0x08; + iec_header.B.org_sample_freq = 0x07; + break; + case 96000: + iec_header.B.sample_freq = 0x0A; + iec_header.B.org_sample_freq = 0x05; + break; + case 176400: + iec_header.B.sample_freq = 0x0C; + iec_header.B.org_sample_freq = 0x03; + break; + case 192000: + iec_header.B.sample_freq = 0x0E; + iec_header.B.org_sample_freq = 0x01; + break; + default: + dev_err(dev, "unsupported sample rate\n"); + return -EFAULT; + } + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + iec_header.B.word_length = 0x02; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iec_header.B.word_length = 0x0b; + break; + default: + return -EFAULT; + } + + return 0; +} + +/* + * The HDMI block transmits the audio data without adding any of the audio + * frame bits. So we have to copy the raw dma data from the ALSA buffer + * to the DMA buffer, adding the frame information. + */ +static int hdmi_dma_copy_user(struct snd_pcm_substream *substream, int channel, + unsigned long pos_bytes, void __user *buf, + unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + u32 *hw_buf; + int subframe_idx; + u32 pcm_data; + + /* Adding frame info to pcm data from userspace and copy to hw_buffer */ + hw_buf = (u32 *)(priv->hw_buffer.area + (pos_bytes * priv->buffer_ratio)); + + while (count > 0) { + for (subframe_idx = 1 ; subframe_idx <= priv->channels ; subframe_idx++) { + if (copy_from_user(&pcm_data, buf, priv->sample_align)) + return -EFAULT; + + buf += priv->sample_align; + count -= priv->sample_align; + + /* Save the header info to the audio dma buffer */ + *hw_buf++ = hdmi_dma_add_frame_info(priv, pcm_data, subframe_idx); + } + + priv->frame_idx++; + if (priv->frame_idx == 192) + priv->frame_idx = 0; + } + + return 0; +} + +static int hdmi_sdma_initbuf(struct device *dev, struct hdmi_dma_priv *priv) +{ + struct hdmi_sdma_script *hdmi_sdma_t = priv->hdmi_sdma_t; + u32 *head, *tail, i; + + if (!hdmi_sdma_t) { + dev_err(dev, "hdmi private addr invalid!!!\n"); + return -EINVAL; + } + + hdmi_sdma_t->control_reg_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_START; + hdmi_sdma_t->status_reg_addr = HDMI_BASE_ADDR + HDMI_IH_AHBDMAAUD_STAT0; + hdmi_sdma_t->dma_start_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_STRADDR0; + + head = &hdmi_sdma_t->buffer[0]; + tail = &hdmi_sdma_t->buffer[1]; + + for (i = 0; i < priv->sdma_params.buffer_num; i++) { + *head = priv->hw_buffer.addr + i * priv->period_bytes * priv->buffer_ratio; + *tail = *head + priv->dma_period_bytes - 1; + head += 2; + tail += 2; + } + + return 0; +} + +static int hdmi_sdma_config(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dai_dev = &priv->pdev->dev; + struct device *dev = component->dev; + struct dma_slave_config slave_config; + int ret; + + priv->dma_channel = dma_request_slave_channel(dai_dev, "tx"); + if (priv->dma_channel == NULL) { + dev_err(dev, "failed to alloc dma channel\n"); + return -EBUSY; + } + + slave_config.direction = DMA_TRANS_NONE; + slave_config.src_addr = (dma_addr_t)priv->sdma_params.buffer_num; + slave_config.dst_addr = (dma_addr_t)priv->sdma_params.phyaddr; + + ret = dmaengine_slave_config(priv->dma_channel, &slave_config); + if (ret) { + dev_err(dev, "failed to config slave dma\n"); + return -EINVAL; + } + + return 0; +} + +static int hdmi_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + if (priv->dma_channel) { + dma_release_channel(priv->dma_channel); + priv->dma_channel = NULL; + } + + return 0; +} + +static int hdmi_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + int ret; + + priv->buffer_bytes = params_buffer_bytes(params); + priv->periods = params_periods(params); + priv->period_bytes = params_period_bytes(params); + priv->channels = params_channels(params); + priv->format = params_format(params); + priv->rate = params_rate(params); + + priv->offset = 0; + priv->period_time = HZ / (priv->rate / params_period_size(params)); + + switch (priv->format) { + case SNDRV_PCM_FORMAT_S16_LE: + priv->buffer_ratio = 2; + priv->sample_align = 2; + priv->sample_bits = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + /* 24 bit audio in 32 bit word */ + priv->buffer_ratio = 1; + priv->sample_align = 4; + priv->sample_bits = 24; + break; + default: + dev_err(dev, "unsupported sample format: %d\n", priv->format); + return -EINVAL; + } + + priv->dma_period_bytes = priv->period_bytes * priv->buffer_ratio; + priv->sdma_params.buffer_num = priv->periods; + priv->sdma_params.phyaddr = priv->phy_hdmi_sdma_t; + + ret = hdmi_sdma_initbuf(dev, priv); + if (ret) + return ret; + + ret = hdmi_sdma_config(substream, priv); + if (ret) + return ret; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + ret = hdmi_dma_configure_dma(dev, priv->channels); + if (ret) + return ret; + + hdmi_dma_set_addr(priv->hw_buffer.addr, priv->dma_period_bytes); + + dumppriv(dev, priv); + + hdmi_dma_update_iec_header(substream); + + /* Init par for mmap optimizate */ + init_table(priv->channels); + + priv->appl_bytes = 0; + + return 0; +} + +static void hdmi_dma_trigger_init(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + unsigned long status; + + priv->offset = 0; + priv->frame_idx = 0; + + /* Copy data by buffer_bytes */ + hdmi_dma_data_copy(substream, priv, 'b'); + + hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1); + + /* Delay after reset */ + udelay(1); + + status = hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0); + hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0); +} + +static int hdmi_dma_prepare_and_submit(struct snd_pcm_substream *substream, + struct hdmi_dma_priv *priv) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + + priv->desc = dmaengine_prep_dma_cyclic(priv->dma_channel, 0, 0, 0, + DMA_TRANS_NONE, 0); + if (!priv->desc) { + dev_err(dev, "failed to prepare slave dma\n"); + return -EINVAL; + } + + priv->desc->callback = hdmi_sdma_callback; + priv->desc->callback_param = (void *)priv; + dmaengine_submit(priv->desc); + + return 0; +} + +static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hdmi_dma_priv *priv = runtime->private_data; + struct device *dev = component->dev; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!check_hdmi_state()) + return 0; + hdmi_dma_trigger_init(substream, priv); + + dumpregs(dev); + + priv->tx_active = true; + hdmi_audio_writeb(AHB_DMA_START, START, 0x1); + hdmi_dma_irq_set(false); + hdmi_set_dma_mode(1); + ret = hdmi_dma_prepare_and_submit(substream, priv); + if (ret) + return ret; + dma_async_issue_pending(priv->desc->chan); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(priv->dma_channel); + hdmi_set_dma_mode(0); + hdmi_dma_irq_set(true); + hdmi_audio_writeb(AHB_DMA_STOP, STOP, 0x1); + priv->tx_active = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + return bytes_to_frames(runtime, priv->offset); +} + +static struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = MXC_HDMI_FORMATS_PLAYBACK, + .rate_min = 32000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = HDMI_PCM_BUF_SIZE, + .period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2, + .period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2, + .periods_min = 8, + .periods_max = 8, + .fifo_size = 0, +}; + +static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv) +{ + unsigned long flags; + + hdmi_writeb(0xff, HDMI_AHB_DMA_POL); + hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL); + + spin_lock_irqsave(&priv->irq_lock, flags); + + hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_dma_irq_set(false); + hdmi_mask(0); + + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + + hdmi_dma_irq_set(true); + hdmi_writeb(0x0, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0); + hdmi_mask(1); + + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static int hdmi_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct hdmi_dma_priv *priv = dev_get_drvdata(dev); + int ret; + + runtime->private_data = priv; + + ret = mxc_hdmi_register_audio(substream); + if (ret < 0) { + dev_err(dev, "HDMI Video is not ready!\n"); + return ret; + } + + hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1); + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + + hdmi_dma_irq_enable(priv); + + return 0; +} + +static int hdmi_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hdmi_dma_priv *priv = runtime->private_data; + + hdmi_dma_irq_disable(priv); + mxc_hdmi_unregister_audio(substream); + + return 0; +} + +static struct snd_pcm_ops imx_hdmi_dma_pcm_ops = { + .open = hdmi_dma_open, + .close = hdmi_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hdmi_dma_hw_params, + .hw_free = hdmi_dma_hw_free, + .trigger = hdmi_dma_trigger, + .pointer = hdmi_dma_pointer, + .copy_user = hdmi_dma_copy_user, +}; + +static int imx_hdmi_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hdmi_dma_priv *priv = dev_get_drvdata(component->dev); + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + u64 dma_mask = DMA_BIT_MASK(32); + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &dma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + HDMI_PCM_BUF_SIZE, &substream->dma_buffer); + if (ret) { + dev_err(card->dev, "failed to alloc playback dma buffer\n"); + return ret; + } + + priv->substream = substream; + + /* Alloc the hw_buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + HDMI_DMA_BUF_SIZE, &priv->hw_buffer); + if (ret) { + dev_err(card->dev, "failed to alloc hw dma buffer\n"); + return ret; + } + + return ret; +} + +static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm) +{ + int stream = SNDRV_PCM_STREAM_PLAYBACK; + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hdmi_dma_priv *priv = dev_get_drvdata(component->dev); + + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + + /* Free the hw_buffer */ + snd_dma_free_pages(&priv->hw_buffer); + priv->hw_buffer.area = NULL; + priv->hw_buffer.addr = 0; +} + +static const struct snd_soc_component_driver imx_hdmi_component = { + .name = DRV_NAME, + .ops = &imx_hdmi_dma_pcm_ops, + .pcm_new = imx_hdmi_dma_pcm_new, + .pcm_free = imx_hdmi_dma_pcm_free, +}; + +static int imx_soc_platform_probe(struct platform_device *pdev) +{ + struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev); + struct hdmi_dma_priv *priv; + int ret = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "Failed to alloc hdmi_dma\n"); + return -ENOMEM; + } + + ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + priv->hdmi_sdma_t = dma_alloc_coherent(&pdev->dev, + sizeof(struct hdmi_sdma_script), + &priv->phy_hdmi_sdma_t, GFP_KERNEL); + if (!priv->hdmi_sdma_t) { + dev_err(&pdev->dev, "Failed to alloc hdmi_sdma_t\n"); + return -ENOMEM; + } + + priv->tx_active = false; + spin_lock_init(&priv->irq_lock); + + priv->pdev = hdmi_drvdata->pdev; + + hdmi_dma_init_iec_header(); + + dev_set_drvdata(&pdev->dev, priv); + + switch (hdmi_readb(HDMI_REVISION_ID)) { + case 0x0a: + snd_imx_hardware.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 4; + snd_imx_hardware.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 4; + break; + default: + break; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &imx_hdmi_component, NULL, 0); + if (ret) + goto err_plat; + + return 0; + +err_plat: + dma_free_coherent(&pdev->dev, sizeof(struct hdmi_sdma_script), + priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t); + + return ret; +} + +static int imx_soc_platform_remove(struct platform_device *pdev) +{ + struct hdmi_dma_priv *priv = dev_get_drvdata(&pdev->dev); + + dma_free_coherent(&pdev->dev, sizeof(struct hdmi_sdma_script), + priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t); + + return 0; +} + +static struct platform_driver imx_hdmi_dma_driver = { + .driver = { + .name = "imx-hdmi-audio", + .owner = THIS_MODULE, + }, + .probe = imx_soc_platform_probe, + .remove = imx_soc_platform_remove, +}; + +module_platform_driver(imx_hdmi_dma_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX HDMI audio DMA"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-hdmi.c b/sound/soc/fsl/imx-hdmi.c new file mode 100644 index 000000000000..f7435831949c --- /dev/null +++ b/sound/soc/fsl/imx-hdmi.c @@ -0,0 +1,117 @@ +/* + * ASoC HDMI Transmitter driver for IMX development boards + * + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. + * + * based on stmp3780_devb_hdmi.c + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/mfd/mxc-hdmi-core.h> +#include <sound/soc.h> + +#include "imx-hdmi.h" + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC("hdmi-audio-codec", "i2s-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("imx-hdmi-audio"))); + +/* imx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link imx_hdmi_dai_link = { + .name = "i.MX HDMI Audio Tx", + .stream_name = "i.MX HDMI Audio Tx", + SND_SOC_DAILINK_REG(hifi), +}; + +static struct snd_soc_card snd_soc_card_imx_hdmi = { + .name = "imx-hdmi-soc", + .dai_link = &imx_hdmi_dai_link, + .num_links = 1, + .owner = THIS_MODULE, +}; + +static int imx_hdmi_audio_probe(struct platform_device *pdev) +{ + struct device_node *hdmi_np, *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_card_imx_hdmi; + struct platform_device *hdmi_pdev; + int ret = 0; + + if (!hdmi_get_registered()) { + dev_err(&pdev->dev, "initialize HDMI-audio failed. load HDMI-video first!\n"); + return -ENODEV; + } + + hdmi_np = of_parse_phandle(np, "hdmi-controller", 0); + if (!hdmi_np) { + dev_err(&pdev->dev, "failed to find hdmi-audio cpudai\n"); + ret = -EINVAL; + goto end; + } + + hdmi_pdev = of_find_device_by_node(hdmi_np); + if (!hdmi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto end; + } + + card->dev = &pdev->dev; + card->dai_link->cpus->dai_name = dev_name(&hdmi_pdev->dev); + + platform_set_drvdata(pdev, card); + + ret = snd_soc_register_card(card); + if (ret) + dev_err(&pdev->dev, "failed to register card: %d\n", ret); + +end: + if (hdmi_np) + of_node_put(hdmi_np); + + return ret; +} + +static int imx_hdmi_audio_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id imx_hdmi_dt_ids[] = { + { .compatible = "fsl,imx-audio-hdmi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids); + +static struct platform_driver imx_hdmi_audio_driver = { + .probe = imx_hdmi_audio_probe, + .remove = imx_hdmi_audio_remove, + .driver = { + .of_match_table = imx_hdmi_dt_ids, + .name = "imx-audio-hdmi", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(imx_hdmi_audio_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX HDMI TX ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-audio-hdmi"); diff --git a/sound/soc/fsl/imx-hdmi.h b/sound/soc/fsl/imx-hdmi.h new file mode 100644 index 000000000000..d06ce9c34d32 --- /dev/null +++ b/sound/soc/fsl/imx-hdmi.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __IMX_HDMI_H +#define __IMX_HDMI_H + +struct imx_hdmi_sdma_params { + dma_addr_t phyaddr; + u32 buffer_num; + int dma; +}; + +struct imx_hdmi { + struct snd_soc_dai_driver cpu_dai_drv; + struct platform_device *codec_dev; + struct platform_device *dma_dev; + struct platform_device *pdev; + struct clk *isfr_clk; + struct clk *iahb_clk; + struct clk *mipi_core_clk; +}; + +#define HDMI_MAX_RATES 7 +#define HDMI_MAX_SAMPLE_SIZE 3 +#define HDMI_MAX_CHANNEL_CONSTRAINTS 4 + +#define MXC_HDMI_RATES_PLAYBACK \ + (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define MXC_HDMI_FORMATS_PLAYBACK \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +union hdmi_audio_header_t { + uint64_t U; + struct { + unsigned consumer:1; + unsigned linear_pcm:1; + unsigned copyright:1; + unsigned pre_emphasis:3; + unsigned mode:2; + + unsigned category_code:8; + + unsigned source:4; + unsigned channel:4; + + unsigned sample_freq:4; + unsigned clock_acc:2; + unsigned reserved0:2; + + unsigned word_length:4; + unsigned org_sample_freq:4; + + unsigned cgms_a:2; + unsigned reserved1:6; + + unsigned reserved2:8; + + unsigned reserved3:8; + } B; + unsigned char status[8]; +}; + +union hdmi_audio_dma_data_t { + uint32_t U; + struct { + unsigned data:24; + unsigned v:1; + unsigned u:1; + unsigned c:1; + unsigned p:1; + unsigned b:1; + unsigned reserved:3; + } B; +}; + +extern union hdmi_audio_header_t iec_header; + +#define hdmi_audio_writeb(reg, bit, val) \ + do { \ + hdmi_mask_writeb(val, HDMI_ ## reg, \ + HDMI_ ## reg ## _ ## bit ## _OFFSET, \ + HDMI_ ## reg ## _ ## bit ## _MASK); \ + pr_debug("Set reg: HDMI_" #reg " (0x%x) "\ + "bit: HDMI_" #reg "_" #bit " (%d) to val: %x\n", \ + HDMI_ ## reg, HDMI_ ## reg ## _ ## bit ## _OFFSET, val); \ + } while (0) + +#endif /* __IMX_HDMI_H */ diff --git a/sound/soc/fsl/imx-i2s-rpmsg.c b/sound/soc/fsl/imx-i2s-rpmsg.c new file mode 100644 index 000000000000..4e75394d0294 --- /dev/null +++ b/sound/soc/fsl/imx-i2s-rpmsg.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2020 NXP + +#include <linux/module.h> +#include <linux/rpmsg.h> +#include <linux/imx_rpmsg.h> + +#include "fsl_rpmsg_i2s.h" + +extern struct i2s_info *i2s_info_g; + +static int i2s_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct i2s_rpmsg_r *msg = (struct i2s_rpmsg_r *)data; + struct i2s_rpmsg *rpmsg; + unsigned long flags; + + dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n", + src, msg->header.cmd, msg->param.resp); + + if (msg->header.type == I2S_TYPE_C) { + if (msg->header.cmd == I2S_TX_PERIOD_DONE) { + spin_lock_irqsave(&i2s_info_g->lock[0], flags); + rpmsg = &i2s_info_g->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM]; + + if (msg->header.major == 1 && msg->header.minor == 2) + rpmsg->recv_msg.param.buffer_tail = + msg->param.buffer_tail; + else + rpmsg->recv_msg.param.buffer_tail++; + + rpmsg->recv_msg.param.buffer_tail %= + i2s_info_g->num_period[0]; + + spin_unlock_irqrestore(&i2s_info_g->lock[0], flags); + i2s_info_g->callback[0](i2s_info_g->callback_param[0]); + + } else if (msg->header.cmd == I2S_RX_PERIOD_DONE) { + spin_lock_irqsave(&i2s_info_g->lock[1], flags); + rpmsg = &i2s_info_g->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM]; + + if (msg->header.major == 1 && msg->header.minor == 2) + rpmsg->recv_msg.param.buffer_tail = + msg->param.buffer_tail; + else + rpmsg->recv_msg.param.buffer_tail++; + + rpmsg->recv_msg.param.buffer_tail %= + i2s_info_g->num_period[1]; + spin_unlock_irqrestore(&i2s_info_g->lock[1], flags); + i2s_info_g->callback[1](i2s_info_g->callback_param[1]); + } + } + + if (msg->header.type == I2S_TYPE_B) { + memcpy(&i2s_info_g->recv_msg, msg, sizeof(struct i2s_rpmsg_r)); + complete(&i2s_info_g->cmd_complete); + } + + return 0; +} + +static int i2s_rpmsg_probe(struct rpmsg_device *rpdev) +{ + struct platform_device *codec_pdev; + struct fsl_rpmsg_i2s *rpmsg_i2s = NULL; + struct fsl_rpmsg_codec rpmsg_codec[3]; + int ret; + + if (!i2s_info_g) + return 0; + + i2s_info_g->rpdev = rpdev; + + init_completion(&i2s_info_g->cmd_complete); + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + rpmsg_i2s = container_of(i2s_info_g, struct fsl_rpmsg_i2s, i2s_info); + + if (rpmsg_i2s->codec_in_dt) + return 0; + + if (rpmsg_i2s->codec_wm8960) { + rpmsg_codec[0].audioindex = rpmsg_i2s->codec_wm8960 >> 16; + rpmsg_codec[0].shared_lrclk = true; + rpmsg_codec[0].capless = false; + codec_pdev = platform_device_register_data( + &rpmsg_i2s->pdev->dev, + RPMSG_CODEC_DRV_NAME_WM8960, + PLATFORM_DEVID_NONE, + &rpmsg_codec[0], sizeof(struct fsl_rpmsg_codec)); + if (IS_ERR(codec_pdev)) { + dev_err(&rpdev->dev, + "failed to register rpmsg audio codec\n"); + ret = PTR_ERR(codec_pdev); + return ret; + } + } + + if (rpmsg_i2s->codec_cs42888) { + rpmsg_codec[1].audioindex = rpmsg_i2s->codec_cs42888 >> 16; + strcpy(rpmsg_codec[1].name, "cs42888"); + rpmsg_codec[1].num_adcs = 2; + + codec_pdev = platform_device_register_data( + &rpmsg_i2s->pdev->dev, + RPMSG_CODEC_DRV_NAME_CS42888, + PLATFORM_DEVID_NONE, + &rpmsg_codec[1], sizeof(struct fsl_rpmsg_codec)); + if (IS_ERR(codec_pdev)) { + dev_err(&rpdev->dev, + "failed to register rpmsg audio codec\n"); + ret = PTR_ERR(codec_pdev); + return ret; + } + } + + if (rpmsg_i2s->codec_ak4497) { + rpmsg_codec[2].audioindex = rpmsg_i2s->codec_ak4497 >> 16; + codec_pdev = platform_device_register_data( + &rpmsg_i2s->pdev->dev, + RPMSG_CODEC_DRV_NAME_AK4497, + PLATFORM_DEVID_NONE, + &rpmsg_codec[2], sizeof(struct fsl_rpmsg_codec)); + if (IS_ERR(codec_pdev)) { + dev_err(&rpdev->dev, + "failed to register rpmsg audio codec\n"); + ret = PTR_ERR(codec_pdev); + return ret; + } + } + + return 0; +} + +static void i2s_rpmsg_remove(struct rpmsg_device *rpdev) +{ + dev_info(&rpdev->dev, "i2s rpmsg driver is removed\n"); +} + +static struct rpmsg_device_id i2s_rpmsg_id_table[] = { + { .name = "rpmsg-audio-channel" }, + { }, +}; + +static struct rpmsg_driver i2s_rpmsg_driver = { + .drv.name = "i2s_rpmsg", + .drv.owner = THIS_MODULE, + .id_table = i2s_rpmsg_id_table, + .probe = i2s_rpmsg_probe, + .callback = i2s_rpmsg_cb, + .remove = i2s_rpmsg_remove, +}; + +static int __init i2s_rpmsg_init(void) +{ + return register_rpmsg_driver(&i2s_rpmsg_driver); +} + +static void __exit i2s_rpmsg_exit(void) +{ + unregister_rpmsg_driver(&i2s_rpmsg_driver); +} +module_init(i2s_rpmsg_init); +module_exit(i2s_rpmsg_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-micfil.c b/sound/soc/fsl/imx-micfil.c new file mode 100644 index 000000000000..13e5c992d195 --- /dev/null +++ b/sound/soc/fsl/imx-micfil.c @@ -0,0 +1,184 @@ +/* + * Copyright 2018 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_micfil.h" + +#define RX 0 +#define TX 1 + +struct imx_micfil_data { + char name[32]; + struct snd_soc_dai_link dai; + struct snd_soc_card card; +}; + +static int imx_micfil_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + int ret; + static u32 support_rates[] = {11025, 16000, 22050, + 32000, 44100, 48000,}; + + constraint_rates.list = support_rates; + constraint_rates.count = ARRAY_SIZE(support_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static int imx_micfil_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = rtd->card->dev; + unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + unsigned int rate = params_rate(params); + int ret, dir; + + /* For playback the XTOR is slave, and for record is master */ + fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; + dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + /* Specific configurations of DAIs starts from here */ + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, + rate, dir); + if (ret) { + dev_err(dev, + "%s: failed to set cpu sysclk: %d\n", __func__, + ret); + return ret; + } + + return 0; +} + +struct snd_soc_ops imx_micfil_ops = { + .startup = imx_micfil_startup, + .hw_params = imx_micfil_hw_params, +}; + +static int imx_micfil_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np; + struct platform_device *cpu_pdev; + struct imx_micfil_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + strncpy(data->name, cpu_np->name, sizeof(data->name) - 1); + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find MICFIL platform device\n"); + ret = -EINVAL; + goto fail; + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + data->dai.name = "micfil hifi"; + data->dai.stream_name = "micfil hifi"; + data->dai.codecs->dai_name = "snd-soc-dummy-dai"; + data->dai.codecs->name = "snd-soc-dummy"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.playback_only = false; + data->dai.ops = &imx_micfil_ops; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + return ret; +} + +static const struct of_device_id imx_micfil_dt_ids[] = { + { .compatible = "fsl,imx-audio-micfil", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_micfil_dt_ids); + +static struct platform_driver imx_micfil_driver = { + .driver = { + .name = "imx-micfil", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_micfil_dt_ids, + }, + .probe = imx_micfil_probe, +}; +module_platform_driver(imx_micfil_driver); + +MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>"); +MODULE_DESCRIPTION("NXP Micfil ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-micfil"); diff --git a/sound/soc/fsl/imx-mqs.c b/sound/soc/fsl/imx-mqs.c new file mode 100644 index 000000000000..6462003dc009 --- /dev/null +++ b/sound/soc/fsl/imx-mqs.c @@ -0,0 +1,291 @@ +/* + * Copyright 2012, 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#define SUPPORT_RATE_NUM 10 + +struct imx_priv { + struct clk *codec_clk; + unsigned int mclk_freq; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; +}; + +static struct imx_priv card_priv; + +static int imx_mqs_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + static u32 support_rates[SUPPORT_RATE_NUM]; + int ret; + + priv->mclk_freq = clk_get_rate(priv->codec_clk); + + if (priv->mclk_freq % 24576000 == 0) { + support_rates[0] = 48000; + constraint_rates.list = support_rates; + constraint_rates.count = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + } else + dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq); + + return 0; +} + +static int imx_mqs_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params)); + if (ret) { + dev_err(card->dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + return 0; +} + +static struct snd_soc_ops imx_mqs_ops = { + .startup = imx_mqs_startup, + .hw_params = imx_mqs_hw_params, +}; + +static struct snd_soc_ops imx_mqs_ops_be = { + .hw_params = imx_mqs_hw_params, +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */ +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "fsl-mqs-dai")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link imx_mqs_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &imx_mqs_ops, + SND_SOC_DAILINK_REG(hifi), + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 0, + .dpcm_merged_chan = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 0, + .ops = &imx_mqs_ops_be, + .be_hw_params_fixup = be_hw_params_fixup, + SND_SOC_DAILINK_REG(hifi_be), + }, +}; + +static struct snd_soc_card snd_soc_card_imx_mqs = { + .name = "mqs-audio", + .dai_link = imx_mqs_dai, + .owner = THIS_MODULE, + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int imx_mqs_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np; + struct imx_priv *priv = &card_priv; + struct platform_device *codec_dev; + struct platform_device *asrc_pdev = NULL; + struct platform_device *cpu_pdev; + struct device_node *asrc_np; + int ret; + u32 width; + + priv->pdev = pdev; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find cpu dai device\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev) { + dev_err(&codec_dev->dev, "failed to find codec device\n"); + ret = -EINVAL; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + imx_mqs_dai[0].codecs->dai_name = "fsl-mqs-dai"; + + if (!asrc_pdev) { + imx_mqs_dai[0].codecs->of_node = codec_np; + imx_mqs_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_mqs_dai[0].platforms->of_node = cpu_np; + snd_soc_card_imx_mqs.num_links = 1; + } else { + imx_mqs_dai[0].codecs->of_node = codec_np; + imx_mqs_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_mqs_dai[0].platforms->of_node = cpu_np; + imx_mqs_dai[1].cpus->of_node = asrc_np; + imx_mqs_dai[1].platforms->of_node = asrc_np; + imx_mqs_dai[2].codecs->of_node = codec_np; + imx_mqs_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev); + snd_soc_card_imx_mqs.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + snd_soc_card_imx_mqs.dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_imx_mqs); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + + return ret; +} + +static const struct of_device_id imx_mqs_dt_ids[] = { + { .compatible = "fsl,imx-audio-mqs", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_mqs_dt_ids); + +static struct platform_driver imx_mqs_driver = { + .driver = { + .name = "imx-mqs", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_mqs_dt_ids, + }, + .probe = imx_mqs_probe, +}; +module_platform_driver(imx_mqs_driver); + +MODULE_AUTHOR("Nicolin Chen <Guangyu.Chen@freescale.com>"); +MODULE_DESCRIPTION("Freescale i.MX MQS ASoC machine driver"); +MODULE_ALIAS("platform:imx-mqs"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-pcm-dma-v2.c b/sound/soc/fsl/imx-pcm-dma-v2.c new file mode 100644 index 000000000000..b5fad925fd0c --- /dev/null +++ b/sound/soc/fsl/imx-pcm-dma-v2.c @@ -0,0 +1,278 @@ +/* + * imx-pcm-dma-v2.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * 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. + */ +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "imx-pcm.h" + +static struct snd_pcm_hardware imx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65532, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool imx_dma_filter_fn(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +/* this may get called several times by oss emulation */ +static int imx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_config config; + struct dma_chan *chan; + int err = 0; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME + */ + if (!dma_data) + return 0; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + chan = snd_dmaengine_pcm_get_chan(substream); + if (!chan) + return -EINVAL; + + /* fills in addr_width and direction */ + err = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (err) + return err; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, + dma_data, + &config); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + config.dst_fifo_num = dma_data->fifo_num; + else + config.src_fifo_num = dma_data->fifo_num; + + return dmaengine_slave_config(chan, &config); +} + +static int imx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static snd_pcm_uframes_t imx_pcm_pointer(struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_pointer(substream); +} + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, + struct device *dev) +{ + size_t size = imx_pcm_hardware.buffer_bytes_max; + int ret; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_IRAM, + dev, + size, + &substream->dma_buffer); + if (ret) + return ret; + + return 0; +} + +static void imx_pcm_free_dma_buffers(struct snd_pcm_substream *substream) +{ + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } +} + +static int imx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_caps dma_caps; + struct dma_chan *chan; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + /* DT boot: filter_data is the DMA name */ + if (rtd->cpu_dai->dev->of_node) { + struct dma_chan *chan; + + chan = dma_request_slave_channel(rtd->cpu_dai->dev, + dma_data->chan_name); + ret = snd_dmaengine_pcm_open(substream, chan); + if (ret) + return ret; + } else { + ret = snd_dmaengine_pcm_open_request_chan(substream, + imx_dma_filter_fn, + dma_data->filter_data); + if (ret) + return ret; + } + + chan = snd_dmaengine_pcm_get_chan(substream); + + ret = dma_get_slave_caps(chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + imx_pcm_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + imx_pcm_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + imx_pcm_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware); + + ret = imx_pcm_preallocate_dma_buffer(substream, chan->device->dev); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; +} + +static int imx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_wc(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static int imx_pcm_close(struct snd_pcm_substream *substream) +{ + imx_pcm_free_dma_buffers(substream); + + return snd_dmaengine_pcm_close_release_chan(substream); +} + +static struct snd_pcm_ops imx_pcm_ops = { + .open = imx_pcm_open, + .close = imx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = imx_pcm_hw_params, + .hw_free = imx_pcm_hw_free, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = imx_pcm_pointer, + .mmap = imx_pcm_mmap, +}; + +static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + return ret; +} + +static struct snd_soc_component_driver imx_soc_platform = { + .name = "imx-pcm-dma-v2", + .ops = &imx_pcm_ops, + .pcm_new = imx_pcm_new, +}; + +int imx_pcm_platform_register(struct device *dev) +{ + return devm_snd_soc_register_component(dev, &imx_soc_platform, NULL, 0); +} +EXPORT_SYMBOL_GPL(imx_pcm_platform_register); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c new file mode 100644 index 000000000000..b4b061bc3414 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -0,0 +1,737 @@ +/* + * imx-rpmsg-platform.c -- ALSA Soc Audio Layer + * + * Copyright 2017 NXP + * + * 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. + */ +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "imx-pcm.h" +#include "fsl_rpmsg_i2s.h" +#include "../../core/pcm_local.h" + +#define DRV_NAME "imx_pcm_rpmsg" + +struct i2s_info *i2s_info_g; +EXPORT_SYMBOL(i2s_info_g); + +static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = IMX_SAI_DMABUF_SIZE, + .period_bytes_min = 512, + .period_bytes_max = 65532, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 6000, + .fifo_size = 0, +}; + +static int imx_rpmsg_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_HW_PARAM]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_HW_PARAM]; + + rpmsg->send_msg.param.rate = params_rate(params); + + if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) + rpmsg->send_msg.param.format = RPMSG_S16_LE; + else if (params_format(params) == SNDRV_PCM_FORMAT_S24_LE) + rpmsg->send_msg.param.format = RPMSG_S24_LE; + else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U16_LE) + rpmsg->send_msg.param.format = SNDRV_PCM_FORMAT_DSD_U16_LE; + else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U32_LE) + rpmsg->send_msg.param.format = SNDRV_PCM_FORMAT_DSD_U32_LE; + else + rpmsg->send_msg.param.format = RPMSG_S32_LE; + + if (params_channels(params) == 1) + rpmsg->send_msg.param.channels = RPMSG_CH_LEFT; + else + rpmsg->send_msg.param.channels = RPMSG_CH_STEREO; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_HW_PARAM; + else + rpmsg->send_msg.header.cmd = I2S_RX_HW_PARAM; + + i2s_info->send_message(rpmsg, i2s_info); + + return 0; +} + +static int imx_rpmsg_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static snd_pcm_uframes_t imx_rpmsg_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + unsigned int pos = 0; + struct i2s_rpmsg *rpmsg; + int buffer_tail = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM]; + + buffer_tail = rpmsg->recv_msg.param.buffer_tail; + pos = buffer_tail * snd_pcm_lib_period_bytes(substream); + + return bytes_to_frames(substream->runtime, pos); +} + +static void imx_rpmsg_timer_callback(struct timer_list *t) +{ + struct stream_timer *stream_timer = + from_timer(stream_timer, t, timer); + struct snd_pcm_substream *substream = stream_timer->substream; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + unsigned long flags; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE; + else + rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE; + + spin_lock_irqsave(&i2s_info->wq_lock, flags); + if (i2s_info->work_write_index != i2s_info->work_read_index) { + int index = i2s_info->work_write_index; + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_write_index++; + i2s_info->work_write_index %= WORK_MAX_NUM; + } else + i2s_info->msg_drop_count[substream->stream]++; + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); +} + +static int imx_rpmsg_pcm_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + int cmd; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_OPEN]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_OPEN]; + + imx_rpmsg_pcm_hardware.buffer_bytes_max = + i2s_info->prealloc_buffer_size; + imx_rpmsg_pcm_hardware.period_bytes_max = + imx_rpmsg_pcm_hardware.buffer_bytes_max / 2; + + snd_soc_set_runtime_hwparams(substream, &imx_rpmsg_pcm_hardware); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_OPEN; + else + rpmsg->send_msg.header.cmd = I2S_RX_OPEN; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM; + i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0; + i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0; + i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0; + } else { + cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM; + i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0; + i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0; + i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0; + } + + i2s_info->send_message(rpmsg, i2s_info); + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + i2s_info->msg_drop_count[substream->stream] = 0; + + /*create thread*/ + i2s_info->stream_timer[substream->stream].substream = substream; + + timer_setup(&i2s_info->stream_timer[substream->stream].timer, + imx_rpmsg_timer_callback, 0); + + return ret; +} + +static int imx_rpmsg_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_CLOSE]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_CLOSE]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_CLOSE; + else + rpmsg->send_msg.header.cmd = I2S_RX_CLOSE; + flush_workqueue(i2s_info->rpmsg_wq); + i2s_info->send_message(rpmsg, i2s_info); + + del_timer(&i2s_info->stream_timer[substream->stream].timer); + + rtd->dai_link->ignore_suspend = 0; + + if (i2s_info->msg_drop_count[substream->stream]) + dev_warn(rtd->dev, "Msg is dropped!, number is %d\n", + i2s_info->msg_drop_count[substream->stream]); + + return ret; +} + +static int imx_rpmsg_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + + /* NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts + * four condition to determine the lpa is enabled. + */ + if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) && + rpmsg_i2s->version == 2 && + rpmsg_i2s->enable_lpa) { + rtd->dai_link->ignore_suspend = 1; + rpmsg_i2s->force_lpa = 1; + } else + rpmsg_i2s->force_lpa = 0; + + return 0; +} + +static int imx_rpmsg_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_wc(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static void imx_rpmsg_pcm_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + + snd_pcm_period_elapsed(substream); +} + +static int imx_rpmsg_pcm_prepare_and_submit(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + unsigned long flags; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_BUFFER]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_BUFFER]; + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_BUFFER; + else + rpmsg->send_msg.header.cmd = I2S_RX_BUFFER; + + rpmsg->send_msg.param.buffer_addr = substream->runtime->dma_addr; + rpmsg->send_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream); + rpmsg->send_msg.param.period_size = snd_pcm_lib_period_bytes(substream); + rpmsg->send_msg.param.buffer_tail = 0; + + i2s_info->num_period[substream->stream] = + rpmsg->send_msg.param.buffer_size / + rpmsg->send_msg.param.period_size; + + i2s_info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete; + i2s_info->callback_param[substream->stream] = substream; + + spin_lock_irqsave(&i2s_info->wq_lock, flags); + if (i2s_info->work_write_index != i2s_info->work_read_index) { + int index = i2s_info->work_write_index; + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_write_index++; + i2s_info->work_write_index %= WORK_MAX_NUM; + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + } else { + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + return -EPIPE; + } + + return 0; +} + +static int imx_rpmsg_async_issue_pending(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + unsigned long flags; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_START]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_START]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_START; + else + rpmsg->send_msg.header.cmd = I2S_RX_START; + + spin_lock_irqsave(&i2s_info->wq_lock, flags); + if (i2s_info->work_write_index != i2s_info->work_read_index) { + int index = i2s_info->work_write_index; + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_write_index++; + i2s_info->work_write_index %= WORK_MAX_NUM; + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + } else { + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + return -EPIPE; + } + + return 0; +} + +static int imx_rpmsg_restart(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + unsigned long flags; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_RESTART]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_RESTART]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_RESTART; + else + rpmsg->send_msg.header.cmd = I2S_RX_RESTART; + + spin_lock_irqsave(&i2s_info->wq_lock, flags); + if (i2s_info->work_write_index != i2s_info->work_read_index) { + int index = i2s_info->work_write_index; + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_write_index++; + i2s_info->work_write_index %= WORK_MAX_NUM; + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + } else { + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + return -EPIPE; + } + return 0; +} + +static int imx_rpmsg_pause(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + unsigned long flags; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_PAUSE]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_PAUSE]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_PAUSE; + else + rpmsg->send_msg.header.cmd = I2S_RX_PAUSE; + + spin_lock_irqsave(&i2s_info->wq_lock, flags); + if (i2s_info->work_write_index != i2s_info->work_read_index) { + int index = i2s_info->work_write_index; + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_write_index++; + i2s_info->work_write_index %= WORK_MAX_NUM; + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + } else { + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + return -EPIPE; + } + return 0; +} + +static int imx_rpmsg_terminate_all(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + int cmd; + unsigned long flags; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_TERMINATE]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_TERMINATE]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_TERMINATE; + else + rpmsg->send_msg.header.cmd = I2S_RX_TERMINATE; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM; + i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0; + i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0; + i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0; + } else { + cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM; + i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0; + i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0; + i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0; + } + + del_timer(&i2s_info->stream_timer[substream->stream].timer); + + spin_lock_irqsave(&i2s_info->wq_lock, flags); + if (i2s_info->work_write_index != i2s_info->work_read_index) { + int index = i2s_info->work_write_index; + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work); + i2s_info->work_write_index++; + i2s_info->work_write_index %= WORK_MAX_NUM; + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + } else { + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + return -EPIPE; + } + + return 0; +} + +int imx_rpmsg_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = imx_rpmsg_pcm_prepare_and_submit(substream); + if (ret) + return ret; + ret = imx_rpmsg_async_issue_pending(substream); + break; + case SNDRV_PCM_TRIGGER_RESUME: + if (rpmsg_i2s->force_lpa) + break; + /* fall through */ + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = imx_rpmsg_restart(substream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!rpmsg_i2s->force_lpa) { + if (runtime->info & SNDRV_PCM_INFO_PAUSE) + ret = imx_rpmsg_pause(substream); + else + ret = imx_rpmsg_terminate_all(substream); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = imx_rpmsg_pause(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + ret = imx_rpmsg_terminate_all(substream); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + return 0; +} + +int imx_rpmsg_pcm_ack(struct snd_pcm_substream *substream) +{ + /*send the hw_avail size through rpmsg*/ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + struct i2s_rpmsg *rpmsg; + int buffer_tail = 0; + int writen_num = 0; + snd_pcm_sframes_t avail; + unsigned long flags; + + if (!rpmsg_i2s->force_lpa) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM]; + else + rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE; + else + rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE; + + rpmsg->send_msg.header.type = I2S_TYPE_C; + + buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) % + snd_pcm_lib_buffer_bytes(substream)); + buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream); + + if (buffer_tail != rpmsg->send_msg.param.buffer_tail) { + writen_num = buffer_tail - rpmsg->send_msg.param.buffer_tail; + if (writen_num < 0) + writen_num += runtime->periods; + + rpmsg->send_msg.param.buffer_tail = buffer_tail; + + spin_lock_irqsave(&i2s_info->lock[substream->stream], flags); + memcpy(&i2s_info->period_done_msg[substream->stream], rpmsg, + sizeof(struct i2s_rpmsg_s)); + + i2s_info->period_done_msg_enabled[substream->stream] = true; + spin_unlock_irqrestore(&i2s_info->lock[substream->stream], flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + avail = snd_pcm_playback_hw_avail(runtime); + else + avail = snd_pcm_capture_hw_avail(runtime); + + if ((avail - writen_num * runtime->period_size) <= runtime->period_size) { + spin_lock_irqsave(&i2s_info->wq_lock, flags); + if (i2s_info->work_write_index != i2s_info->work_read_index) { + int index = i2s_info->work_write_index; + memcpy(&i2s_info->work_list[index].msg, rpmsg, + sizeof(struct i2s_rpmsg_s)); + queue_work(i2s_info->rpmsg_wq, + &i2s_info->work_list[index].work); + i2s_info->work_write_index++; + i2s_info->work_write_index %= WORK_MAX_NUM; + } else + i2s_info->msg_drop_count[substream->stream]++; + spin_unlock_irqrestore(&i2s_info->wq_lock, flags); + } else { + if (rpmsg_i2s->force_lpa && !timer_pending(&i2s_info->stream_timer[substream->stream].timer)) { + int time_msec; + time_msec = (int)(runtime->period_size*1000/runtime->rate); + mod_timer(&i2s_info->stream_timer[substream->stream].timer, + jiffies + msecs_to_jiffies(time_msec)); + } + } + } + + return 0; +} + +static struct snd_pcm_ops imx_rpmsg_pcm_ops = { + .open = imx_rpmsg_pcm_open, + .close = imx_rpmsg_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = imx_rpmsg_pcm_hw_params, + .hw_free = imx_rpmsg_pcm_hw_free, + .trigger = imx_rpmsg_pcm_trigger, + .pointer = imx_rpmsg_pcm_pointer, + .mmap = imx_rpmsg_pcm_mmap, + .ack = imx_rpmsg_pcm_ack, + .prepare = imx_rpmsg_pcm_prepare, +}; + +static int imx_rpmsg_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream, int size) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_wc(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void imx_rpmsg_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = SNDRV_PCM_STREAM_PLAYBACK; + stream < SNDRV_PCM_STREAM_LAST; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_wc(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int imx_rpmsg_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev); + struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK, + i2s_info->prealloc_buffer_size); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE, + i2s_info->prealloc_buffer_size); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + imx_rpmsg_pcm_free_dma_buffers(pcm); + + return ret; +} + +static struct snd_soc_component_driver imx_rpmsg_soc_component = { + .name = DRV_NAME, + .ops = &imx_rpmsg_pcm_ops, + .pcm_new = imx_rpmsg_pcm_new, + .pcm_free = imx_rpmsg_pcm_free_dma_buffers, +}; + +int imx_rpmsg_platform_register(struct device *dev) +{ + struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev); + struct snd_soc_component *component; + int ret; + + i2s_info_g = &rpmsg_i2s->i2s_info; + + ret = devm_snd_soc_register_component(dev, &imx_rpmsg_soc_component, + NULL, 0); + if (ret) + return ret; + + component = snd_soc_lookup_component(dev, DRV_NAME); + if (!component) + return -EINVAL; + +#ifdef CONFIG_DEBUG_FS + component->debugfs_prefix = "dma"; +#endif + + return 0; +} +EXPORT_SYMBOL_GPL(imx_rpmsg_platform_register); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h index 5dd406774d3e..edbb268332bd 100644 --- a/sound/soc/fsl/imx-pcm.h +++ b/sound/soc/fsl/imx-pcm.h @@ -39,13 +39,27 @@ struct imx_pcm_fiq_params { struct snd_dmaengine_dai_dma_data *dma_params_tx; }; +#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_RPMSG) +int imx_rpmsg_platform_register(struct device *dev); +#else +static inline int imx_rpmsg_platform_register(struct device *dev) +{ + return -ENODEV; +} +#endif + #if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA) int imx_pcm_dma_init(struct platform_device *pdev, size_t size); +int imx_pcm_platform_register(struct device *dev); #else static inline int imx_pcm_dma_init(struct platform_device *pdev, size_t size) { return -ENODEV; } +static inline int imx_pcm_platform_register(struct device *dev) +{ + return -ENODEV; +} #endif #if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ) diff --git a/sound/soc/fsl/imx-pcm512x.c b/sound/soc/fsl/imx-pcm512x.c new file mode 100644 index 000000000000..87d8028ab51b --- /dev/null +++ b/sound/soc/fsl/imx-pcm512x.c @@ -0,0 +1,539 @@ +/* i.MX pcm512x audio support + * + * Copyright 2020 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> + +#include "fsl_sai.h" +#include "../codecs/pcm512x.h" + +#define DAC_CLK_EXT_44K 22579200UL +#define DAC_CLK_EXT_48K 24576000UL + +struct imx_pcm512x_data { + struct snd_soc_dai_link dai_link[3]; + struct snd_soc_card card; + struct snd_soc_codec_conf *codec_conf; + struct gpio_desc *mute_gpio; + int num_codec_conf; + unsigned int slots; + unsigned int slot_width; + unsigned int daifmt; + bool dac_sclk; + bool dac_pluspro; + bool dac_led_status; + bool dac_gain_limit; + bool dac_gpio_unmute; + bool dac_auto_mute; + bool one2one_ratio; + bool tdm_mode; +}; + +enum ext_osc { + DAC_CLK_INT, + DAC_CLK_EXT_44EN, + DAC_CLK_EXT_48EN, +}; + +static const struct imx_pcm512x_fs_map { + unsigned int rmin; + unsigned int rmax; + unsigned int wmin; + unsigned int wmax; +} fs_map[] = { + /* Normal, < 32kHz */ + { .rmin = 8000, .rmax = 24000, .wmin = 1024, .wmax = 1024, }, + /* Normal, 32kHz */ + { .rmin = 32000, .rmax = 32000, .wmin = 256, .wmax = 1024, }, + /* Normal */ + { .rmin = 44100, .rmax = 48000, .wmin = 256, .wmax = 768, }, + /* Double */ + { .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 512, }, + /* Quad */ + { .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 256, }, + /* Oct */ + { .rmin = 352800, .rmax = 384000, .wmin = 32, .wmax = 128, }, + /* Hex */ + { .rmin = 705600, .rmax = 768000, .wmin = 16, .wmax = 64, }, +}; + +static const u32 pcm512x_rates[] = { + 8000, 11025, 16000, 22050, 32000, + 44100, 48000, 64000, 88200, 96000, + 176400, 192000, 352800, 384000, +}; + +static int imx_pcm512x_select_ext_clk(struct snd_soc_component *comp, + int ext_osc) +{ + switch(ext_osc) { + case DAC_CLK_INT: + snd_soc_component_update_bits(comp, PCM512x_GPIO_CONTROL_1, 0x24, 0x00); + break; + case DAC_CLK_EXT_44EN: + snd_soc_component_update_bits(comp, PCM512x_GPIO_CONTROL_1, 0x24, 0x20); + break; + case DAC_CLK_EXT_48EN: + snd_soc_component_update_bits(comp, PCM512x_GPIO_CONTROL_1, 0x24, 0x04); + break; + } + + return 0; +} + +static bool imx_pcm512x_is_sclk(struct snd_soc_component *comp) +{ + unsigned int sclk; + + snd_soc_component_read(comp, PCM512x_RATE_DET_4, &sclk); + + return (!(sclk & 0x40)); +} + +static bool imx_pcm512x_is_sclk_sleep(struct snd_soc_component *comp) +{ + msleep(2); + return imx_pcm512x_is_sclk(comp); +} + +static int imx_pcm512x_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct imx_pcm512x_data *data = snd_soc_card_get_drvdata(card); + int ret; + + if (data->dac_gain_limit) { + ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207); + if (ret) + dev_warn(card->dev, "fail to set volume limit"); + } + + return 0; +} + +static int imx_pcm512x_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level) +{ + struct imx_pcm512x_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + codec_dai = rtd->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + /* unmute amp */ + gpiod_set_value_cansleep(data->mute_gpio, 1); + break; + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + /* mute amp */ + gpiod_set_value_cansleep(data->mute_gpio, 0); + default: + break; + } + + return 0; +} + +static unsigned long pcm512x_get_mclk_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_pcm512x_data *data = snd_soc_card_get_drvdata(rtd->card); + unsigned int width = data->slots * data->slot_width; + unsigned int rate = params_rate(params); + int i; + + for (i = 0; i < ARRAY_SIZE(fs_map); i++) { + if (rate >= fs_map[i].rmin && rate <= fs_map[i].rmax) { + width = max(width, fs_map[i].wmin); + width = min(width, fs_map[i].wmax); + /* Adjust SAI bclk:mclk ratio */ + width *= data->one2one_ratio ? 1 : 2; + + return rate * width; + } + } + + /* Let DAI manage clk frequency by default */ + return 0; +} + +static int imx_pcm512x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *comp = codec_dai->component; + struct snd_soc_card *card = rtd->card; + struct imx_pcm512x_data *data = snd_soc_card_get_drvdata(card); + unsigned int channels = params_channels(params); + unsigned int sample_rate = params_rate(params); + unsigned long mclk_freq; + int ret, i; + + data->slots = 2; + data->slot_width = params_physical_width(params); + + ret = snd_soc_dai_set_fmt(cpu_dai, data->daifmt); + if (ret) { + dev_err(card->dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + BIT(channels) - 1, BIT(channels) - 1, + data->slots, data->slot_width); + if (ret) { + dev_err(card->dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, data->slots * data->slot_width); + if (ret) { + dev_err(card->dev, "failed to set cpu dai bclk ratio\n"); + return ret; + } + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + ret = snd_soc_dai_set_fmt(codec_dai, data->daifmt); + if (ret) { + dev_err(card->dev, "failed to set codec dai[%d] fmt: %d\n", + i, ret); + return ret; + } + + ret = snd_soc_dai_set_bclk_ratio(codec_dai, + data->slots * data->slot_width); + if (ret) { + dev_err(card->dev, "failed to set cpu dai bclk ratio\n"); + return ret; + } + } + + /* set MCLK freq */ + if (data->dac_pluspro && data->dac_sclk) { + if (do_div(sample_rate, 8000)) { + mclk_freq = DAC_CLK_EXT_44K; + imx_pcm512x_select_ext_clk(comp, DAC_CLK_EXT_44EN); + ret = snd_soc_dai_set_sysclk(codec_dai, + PCM512x_SYSCLK_MCLK1, mclk_freq, SND_SOC_CLOCK_IN); + } else { + mclk_freq = DAC_CLK_EXT_48K; + imx_pcm512x_select_ext_clk(comp, DAC_CLK_EXT_48EN); + ret = snd_soc_dai_set_sysclk(codec_dai, + PCM512x_SYSCLK_MCLK2, mclk_freq, SND_SOC_CLOCK_IN); + } + if (ret < 0) + dev_err(card->dev, "failed to set cpu dai mclk rate (%lu): %d\n", + mclk_freq, ret); + } else { + mclk_freq = pcm512x_get_mclk_rate(substream, params); + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, + mclk_freq, SND_SOC_CLOCK_OUT); + if (ret < 0) + dev_err(card->dev, "failed to set cpu dai mclk1 rate (%lu): %d\n", + mclk_freq, ret); + } + + return ret; +} + +static int imx_pcm512x_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_pcm512x_data *data = snd_soc_card_get_drvdata(card); + static struct snd_pcm_hw_constraint_list constraint_rates; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *comp = codec_dai->component; + bool ext_44sclk, ext_48sclk, ext_nosclk; + int ret; + + constraint_rates.list = pcm512x_rates; + constraint_rates.count = ARRAY_SIZE(pcm512x_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraint_rates); + if (ret) + return ret; + + if (data->dac_led_status) { + snd_soc_component_update_bits(comp, PCM512x_GPIO_EN, 0x08, 0x08); + snd_soc_component_update_bits(comp, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02); + snd_soc_component_update_bits(comp, PCM512x_GPIO_CONTROL_1, 0x08, 0x08); + } + + if (data->dac_sclk) { + snd_soc_component_update_bits(comp, PCM512x_GPIO_EN, 0x24, 0x24); + snd_soc_component_update_bits(comp, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02); + snd_soc_component_update_bits(comp, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02); + + imx_pcm512x_select_ext_clk(comp, DAC_CLK_EXT_44EN); + ext_44sclk = imx_pcm512x_is_sclk_sleep(comp); + + imx_pcm512x_select_ext_clk(comp, DAC_CLK_EXT_48EN); + ext_48sclk = imx_pcm512x_is_sclk_sleep(comp); + + imx_pcm512x_select_ext_clk(comp, DAC_CLK_INT); + ext_nosclk = imx_pcm512x_is_sclk_sleep(comp); + + data->dac_pluspro = (ext_44sclk && ext_48sclk && !ext_nosclk); + } + + return 0; +} + +static void imx_pcm512x_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_pcm512x_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *comp = codec_dai->component; + + if (data->dac_led_status) + snd_soc_component_update_bits(comp, PCM512x_GPIO_CONTROL_1, 0x08, 0x00); +} + +static struct snd_soc_ops imx_pcm512x_ops = { + .hw_params = imx_pcm512x_hw_params, + .startup = imx_pcm512x_startup, + .shutdown = imx_pcm512x_shutdown, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link_component pcm512x_codecs[] = { + { + /* Playback */ + .dai_name = "pcm512x-hifi", + }, +}; + +static struct snd_soc_dai_link imx_pcm512x_dai[] = { + { + .name = "pcm512x-audio", + .stream_name = "audio", + .codecs = pcm512x_codecs, + .num_codecs = 1, + .ops = &imx_pcm512x_ops, + .init = imx_pcm512x_dai_init, + .ignore_pmdown_time = 1, + SND_SOC_DAILINK_REG(hifi), + }, +}; + +static int imx_pcm512x_probe(struct platform_device *pdev) +{ + struct device_node *bitclkmaster, *framemaster = NULL; + struct device_node *cpu_np, *codec_np = NULL; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev = NULL; + struct snd_soc_dai_link_component *comp; + struct imx_pcm512x_data *data; + struct i2c_client *codec_dev; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL); + if (!comp) + return -ENOMEM; + + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "audio dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "audio codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + data->dac_gain_limit = of_property_read_bool(np, "dac,24db_digital_gain"); + data->dac_auto_mute = of_property_read_bool(np, "dac,auto_mute_amp"); + data->dac_gpio_unmute = of_property_read_bool(np, "dac,unmute_amp"); + data->dac_led_status = of_property_read_bool(np, "dac,led_status"); + + if (data->dac_auto_mute || data->dac_gpio_unmute) { + data->mute_gpio = devm_gpiod_get_optional(&pdev->dev, + "mute-amp", GPIOD_OUT_LOW); + if (IS_ERR(data->mute_gpio)) { + dev_err(&pdev->dev, "failed to get mute amp gpio\n"); + ret = PTR_ERR(data->mute_gpio); + goto fail; + } + } + + if (data->dac_auto_mute && data->dac_gpio_unmute) + data->card.set_bias_level = imx_pcm512x_set_bias_level; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + memcpy(data->dai_link, imx_pcm512x_dai, + sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(data->dai_link)); + + data->card.owner = THIS_MODULE; + data->card.dev = &pdev->dev; + data->card.dai_link = data->dai_link; + data->card.num_links = 1; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(&pdev->dev, "failed to find card model name\n"); + goto fail; + } + + if (of_property_read_bool(np, "audio-routing")) { + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing\n"); + goto fail; + } + } + + if (of_property_read_bool(np, "audio-widgets")) { + ret = snd_soc_of_parse_audio_simple_widgets(&data->card, "audio-widgets"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-widgets\n"); + goto fail; + } + } + + data->daifmt = snd_soc_of_parse_daifmt(np, NULL, &bitclkmaster, &framemaster); + data->daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + + if (codec_np == bitclkmaster) + data->daifmt |= (codec_np == framemaster) ? + SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; + else + data->daifmt |= (codec_np == framemaster) ? + SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; + + if (!bitclkmaster) + of_node_put(bitclkmaster); + if (!framemaster) + of_node_put(framemaster); + + if (of_property_read_bool(codec_np, "clocks")) + data->dac_sclk = true; + + data->dai_link[0].cpus = &comp[0]; + data->dai_link[0].num_cpus = 1; + data->dai_link[0].codecs = &comp[1]; + data->dai_link[0].num_codecs = 1; + data->dai_link[0].platforms = &comp[2]; + data->dai_link[0].num_platforms = 1; + + data->dai_link[0].cpus->of_node = cpu_np; + data->dai_link[0].platforms->of_node = cpu_np; + data->dai_link[0].codecs->of_node = codec_np; + data->dai_link[0].codecs->dai_name = "pcm512x-hifi"; + data->dai_link[0].dai_fmt = data->daifmt; + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + if (data->dac_gpio_unmute && data->dac_auto_mute) + gpiod_set_value_cansleep(data->mute_gpio, 1); + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int imx_pcm512x_remove(struct platform_device *pdev) +{ + struct imx_pcm512x_data *data = platform_get_drvdata(pdev); + + if (data->mute_gpio) + gpiod_set_value_cansleep(data->mute_gpio, 0); + + return snd_soc_unregister_card(&data->card); +} + +static const struct of_device_id imx_pcm512x_dt_ids[] = { + { .compatible = "fsl,imx-audio-pcm512x", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_pcm512x_dt_ids); + +static struct platform_driver imx_pcm512x_driver = { + .driver = { + .name = "imx-pcm512x", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_pcm512x_dt_ids, + }, + .probe = imx_pcm512x_probe, + .remove = imx_pcm512x_remove, +}; +module_platform_driver(imx_pcm512x_driver); + +MODULE_DESCRIPTION("NXP i.MX pcm512x ASoC machine driver"); +MODULE_AUTHOR("Adrian Alonso <adrian.alonso@nxp.com>"); +MODULE_ALIAS("platform:imx-pcm512x"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pdm.c b/sound/soc/fsl/imx-pdm.c new file mode 100644 index 000000000000..6627227e0161 --- /dev/null +++ b/sound/soc/fsl/imx-pdm.c @@ -0,0 +1,409 @@ +/* + * Copyright 2017-2020 NXP. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <sound/soc.h> + +#include "fsl_sai.h" + +#define IMX_PDM_FORMATS (SNDRV_PCM_FMTBIT_DSD_U8 | \ + SNDRV_PCM_FMTBIT_DSD_U16_LE | \ + SNDRV_PCM_FMTBIT_DSD_U32_LE) + +struct imx_pdm_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + unsigned int decimation; + unsigned long mclk_11k; + unsigned long mclk_8k; + bool fixed_mclk; + int osr_id; +}; + +static const struct imx_pdm_mic_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +} fs_mul[] = { + { .min = 8000, .max = 11025, .mul = 8 }, /* low power */ + { .min = 16000, .max = 64000, .mul = 16 }, /* performance */ +}; + +/* Ratio based on default Audio PLLs + * Audio PLL1 = 393216000 Hz + * Audio PLL2 = 361267200 Hz + */ +static const struct imx_pdm_mic_mclk_fixed { + unsigned long mclk_11k; + unsigned long mclk_8k; + unsigned int ratio; +} mclk_fixed[] = { + { .mclk_11k = 11289600, .mclk_8k = 12288000, .ratio = 32 }, + { .mclk_11k = 15052800, .mclk_8k = 16384000, .ratio = 24 }, + { .mclk_11k = 22579200, .mclk_8k = 24576000, .ratio = 16 }, + { .mclk_11k = 45158400, .mclk_8k = 49152000, .ratio = 8 }, +}; + +static const unsigned int imx_pdm_mic_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 64000, +}; + +static const struct imx_pdm_mic_osr_map { + int id; + unsigned int osr; +} osr_map[] = { + { .id = 0, .osr = 48 }, /* 4x12 */ + { .id = 1, .osr = 64 }, /* 4x16 */ + { .id = 2, .osr = 96 }, /* 4x24 */ + { .id = 3, .osr = 128 }, /* 4x32 */ + { .id = 4, .osr = 192 }, /* 4x48 */ +}; + +static int imx_pdm_mic_get_osr_id(int decimation) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(osr_map); i++) { + if (osr_map[i].osr == decimation) + return osr_map[i].id; + } + + return -EINVAL; +} + +static unsigned int imx_pdm_mic_get_osr_rate(int osr_id) +{ + int i; + + for (i = 0; ARRAY_SIZE(osr_map); i++) { + if (osr_map[i].id == osr_id) + return osr_map[i].osr; + } + + return -EINVAL; +} + +static const char *const osr_rate_text[] = { + "OSR_4x12", + "OSR_4x16", + "OSR_4x24", + "OSR_4x32", + "OSR_4x48" +}; + +static int osr_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_pdm_data *data = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = data->osr_id; + + return 0; +} + +static int osr_rate_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_pdm_data *data = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + int osr = snd_soc_enum_item_to_val(e, item[0]); + + data->decimation = imx_pdm_mic_get_osr_rate(osr); + data->osr_id = osr; + + return 0; +} + +static const struct soc_enum osr_rate_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(osr_rate_text), osr_rate_text); + +const struct snd_kcontrol_new imx_pdm_mic_snd_ctrls[] = { + SOC_ENUM_EXT("over sampling ratio", osr_rate_enum, + osr_rate_get, osr_rate_set), +}; + +static struct snd_pcm_hw_constraint_list imx_pdm_mic_rate_constrains = { + .count = ARRAY_SIZE(imx_pdm_mic_rates), + .list = imx_pdm_mic_rates, +}; +static unsigned int imx_pdm_mic_channels[] = { 1, 2, 4, 6, 8 }; +static struct snd_pcm_hw_constraint_list imx_pdm_mic_channels_constrains = { + .count = ARRAY_SIZE(imx_pdm_mic_channels), + .list = imx_pdm_mic_channels, +}; + +static unsigned long imx_pdm_mic_mclk_freq(unsigned int decimation, + unsigned int rate) +{ + int i; + + /* Find appropriate mclk freq */ + for (i = 0; i < ARRAY_SIZE(fs_mul); i++) { + if (rate >= fs_mul[i].min && rate <= fs_mul[i].max) + return (rate * decimation * fs_mul[i].mul); + } + + return 0; +} + +static int imx_pdm_mic_get_mclk_fixed(struct imx_pdm_data *data, + unsigned int ratio) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mclk_fixed); i++) { + if (mclk_fixed[i].ratio == ratio) { + data->mclk_11k = mclk_fixed[i].mclk_11k; + data->mclk_8k = mclk_fixed[i].mclk_8k; + return 0; + } + } + + return -EINVAL; +} + +static int imx_pdm_mic_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &imx_pdm_mic_rate_constrains); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw rate constrains: %d\n", ret); + return ret; + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, &imx_pdm_mic_channels_constrains); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw channels constrains: %d\n", ret); + return ret; + } + + ret = snd_pcm_hw_constraint_mask64(runtime, + SNDRV_PCM_HW_PARAM_FORMAT, IMX_PDM_FORMATS); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw format constrains: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_pdm_mic_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_pdm_data *data = snd_soc_card_get_drvdata(card); + unsigned int sample_rate = params_rate(params); + unsigned long mclk_freq; + int ret; + + /* set cpu dai format configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_PDM | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(card->dev, "fail to set cpu dai fmt: %d\n", ret); + return ret; + } + + /* Set bitclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, data->decimation); + if (ret) { + dev_err(card->dev, "fail to set cpu sysclk: %d\n", ret); + return ret; + } + + if (data->fixed_mclk) { + mclk_freq = (do_div(sample_rate, 8000) ? + data->mclk_11k : data->mclk_8k); + } else { + mclk_freq = imx_pdm_mic_mclk_freq(data->decimation, + sample_rate); + } + /* set mclk freq */ + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, + mclk_freq, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(card->dev, "fail to set cpu mclk1 rate: %lu\n", + mclk_freq); + return ret; + } + + dev_dbg(card->dev, "mclk: %lu, bclk ratio: %u\n", + mclk_freq, data->decimation); + + return 0; +} + +static const struct snd_soc_ops imx_pdm_mic_ops = { + .startup = imx_pdm_mic_startup, + .hw_params = imx_pdm_mic_hw_params, +}; + +static int imx_pdm_mic_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np = NULL; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev; + struct imx_pdm_data *data; + struct snd_soc_dai_link_component *dlc; + unsigned long sai_mclk, sai_pll8k; + struct fsl_sai *sai; + unsigned int ratio; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "fail to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + ret = of_property_read_u32(np, "decimation", &data->decimation); + if (ret < 0) { + ret = -EINVAL; + goto fail; + } + + data->osr_id = imx_pdm_mic_get_osr_id(data->decimation); + if (data->osr_id < 0) { + ret = -EINVAL; + goto fail; + } + + if (of_find_property(np, "fixed-mclk", NULL)) + data->fixed_mclk = true; + + if (data->fixed_mclk) { + sai = dev_get_drvdata(&cpu_pdev->dev); + /* Get SAI clock settings */ + sai_mclk = clk_get_rate(sai->mclk_clk[FSL_SAI_CLK_MAST1]); + sai_pll8k = clk_get_rate(sai->pll8k_clk); + ratio = sai_pll8k / sai_mclk; + + ret = imx_pdm_mic_get_mclk_fixed(data, ratio); + if (ret) { + dev_err(&pdev->dev, "fail to set fixed mclk: %d\n", ret); + return ret; + } + + dev_dbg(&pdev->dev, "sai_pll8k: %lu, sai_mclk: %lu, ratio: %u\n", + sai_pll8k, sai_mclk, ratio); + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "pdm hifi"; + data->dai.stream_name = "pdm hifi"; + data->dai.codecs->dai_name = "snd-soc-dummy-dai"; + data->dai.codecs->name = "snd-soc-dummy"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.capture_only = 1; + data->dai.ops = &imx_pdm_mic_ops; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(&pdev->dev, "fail to find card model name\n"); + goto fail; + } + + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.controls = imx_pdm_mic_snd_ctrls; + data->card.num_controls = ARRAY_SIZE(imx_pdm_mic_snd_ctrls); + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd soc register card failed: %d\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + + return ret; +} + +static int imx_pdm_mic_remove(struct platform_device *pdev) +{ + struct imx_pdm_data *data = platform_get_drvdata(pdev); + /* unregister card */ + snd_soc_unregister_card(&data->card); + return 0; +} + +static const struct of_device_id imx_pdm_mic_dt_ids[] = { + { .compatible = "fsl,imx-pdm-mic", }, + { /* sentinel*/ } +}; +MODULE_DEVICE_TABLE(of, imx_pdm_mic_dt_ids); + +static struct platform_driver imx_pdm_mic_driver = { + .driver = { + .name = "imx-pdm-mic", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_pdm_mic_dt_ids, + }, + .probe = imx_pdm_mic_probe, + .remove = imx_pdm_mic_remove, +}; +module_platform_driver(imx_pdm_mic_driver); + +MODULE_DESCRIPTION("NXP i.MX PDM mic ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-pdm-mic"); diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c new file mode 100644 index 000000000000..043e4900befc --- /dev/null +++ b/sound/soc/fsl/imx-rpmsg.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/of_reserved_mem.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_rpmsg_i2s.h" + +struct imx_rpmsg_data { + struct snd_soc_dai_link dai[1]; + struct snd_soc_card card; +}; + +static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Main MIC", NULL), +}; + +static int imx_rpmsg_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np; + struct device_node *codec_np = NULL; + struct platform_device *cpu_pdev; + struct imx_rpmsg_data *data; + struct fsl_rpmsg_i2s *rpmsg_i2s; + struct snd_soc_dai_link_component *dlc; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find rpmsg platform device\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_reserved_mem_device_init_by_idx(&pdev->dev, pdev->dev.of_node, 0); + if (ret) + dev_warn(&pdev->dev, "no reserved DMA memory for rpmsg device\n"); + + rpmsg_i2s = platform_get_drvdata(cpu_pdev); + + if (rpmsg_i2s->codec_in_dt) { + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + } + + data->dai[0].cpus = &dlc[0]; + data->dai[0].num_cpus = 1; + data->dai[0].platforms = &dlc[1]; + data->dai[0].num_platforms = 1; + data->dai[0].codecs = &dlc[2]; + data->dai[0].num_codecs = 1; + + data->dai[0].name = "rpmsg hifi"; + data->dai[0].stream_name = "rpmsg hifi"; + data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + if (rpmsg_i2s->codec_wm8960) { + if (rpmsg_i2s->codec_in_dt) { + data->dai[0].codecs->of_node = codec_np; + data->dai[0].codecs->dai_name = "rpmsg-wm8960-hifi"; + } else { + data->dai[0].codecs->dai_name = "rpmsg-wm8960-hifi"; + data->dai[0].codecs->name = "rpmsg-audio-codec-wm8960"; + } + } + + if (rpmsg_i2s->codec_dummy) { + data->dai[0].codecs->dai_name = "snd-soc-dummy-dai"; + data->dai[0].codecs->name = "snd-soc-dummy"; + } + + if (rpmsg_i2s->codec_ak4497) { + data->dai[0].codecs->dai_name = "rpmsg-ak4497-aif"; + data->dai[0].codecs->name = "rpmsg-audio-codec-ak4497"; + data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + } + + data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].platforms->of_node = cpu_np; + data->dai[0].playback_only = true; + data->dai[0].capture_only = true; + data->card.num_links = 1; + data->card.dai_link = data->dai; + + if (of_property_read_bool(pdev->dev.of_node, "rpmsg-out")) + data->dai[0].capture_only = false; + + if (of_property_read_bool(pdev->dev.of_node, "rpmsg-in")) + data->dai[0].playback_only = false; + + if (data->dai[0].playback_only && data->dai[0].capture_only) { + dev_err(&pdev->dev, "no enabled rpmsg DAI link\n"); + ret = -EINVAL; + goto fail; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + if (rpmsg_i2s->codec_wm8960) { + ret = snd_soc_of_parse_audio_routing(&data->card, + "audio-routing"); + if (ret) + goto fail; + + data->card.dapm_widgets = imx_wm8960_dapm_widgets; + data->card.num_dapm_widgets = + ARRAY_SIZE(imx_wm8960_dapm_widgets); + } + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + return ret; +} + +static const struct of_device_id imx_rpmsg_dt_ids[] = { + { .compatible = "fsl,imx-audio-rpmsg", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_rpmsg_dt_ids); + +static struct platform_driver imx_rpmsg_driver = { + .driver = { + .name = "imx-audio-rpmsg", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_rpmsg_dt_ids, + }, + .probe = imx_rpmsg_probe, +}; +module_platform_driver(imx_rpmsg_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX rpmsg audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-rpmsg"); diff --git a/sound/soc/fsl/imx-si476x.c b/sound/soc/fsl/imx-si476x.c new file mode 100644 index 000000000000..a5112a7d8d5a --- /dev/null +++ b/sound/soc/fsl/imx-si476x.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008-2016 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <sound/soc.h> + +#include "imx-audmux.h" + +static int imx_audmux_config(int slave, int master) +{ + unsigned int ptcr, pdcr; + slave = slave - 1; + master = master - 1; + + ptcr = IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(slave) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(slave); + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(slave); + imx_audmux_v2_configure_port(master, ptcr, pdcr); + + /* + * According to RM, RCLKDIR and SYN should not be changed at same time. + * So separate to two step for configuring this port. + */ + ptcr |= IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RFSEL(slave) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_RCSEL(slave); + imx_audmux_v2_configure_port(master, ptcr, pdcr); + + ptcr = IMX_AUDMUX_V2_PTCR_SYN; + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(master); + imx_audmux_v2_configure_port(slave, ptcr, pdcr); + + return 0; +} + +static int imx_si476x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(cpu_dai->dev, "failed to set dai fmt\n"); + return ret; + } + + return ret; +} + +static struct snd_soc_ops imx_si476x_ops = { + .hw_params = imx_si476x_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "si476x-codec")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link imx_dai = { + .name = "imx-si476x", + .stream_name = "imx-si476x", + .ops = &imx_si476x_ops, + SND_SOC_DAILINK_REG(hifi), +}; + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-audio-si476x", + .dai_link = &imx_dai, + .num_links = 1, + .owner = THIS_MODULE, +}; + +static int imx_si476x_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_card_imx_3stack; + struct device_node *ssi_np, *np = pdev->dev.of_node; + struct platform_device *ssi_pdev; + struct i2c_client *fm_dev; + struct device_node *fm_np = NULL; + int int_port, ext_port, ret; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + return ret; + } + + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + imx_audmux_config(int_port, ext_port); + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + if (!ssi_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + return -EINVAL; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto end; + } + + fm_np = of_parse_phandle(pdev->dev.of_node, "fm-controller", 0); + if (!fm_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto end; + } + + fm_dev = of_find_i2c_device_by_node(fm_np->parent); + if (!fm_dev || !fm_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find FM platform device\n"); + ret = -EINVAL; + goto end; + } + + card->dev = &pdev->dev; + card->dai_link->cpus->dai_name = dev_name(&ssi_pdev->dev); + card->dai_link->platforms->of_node = ssi_np; + card->dai_link->codecs->of_node = fm_np; + + platform_set_drvdata(pdev, card); + + ret = snd_soc_register_card(card); + if (ret) + dev_err(&pdev->dev, "Failed to register card: %d\n", ret); + +end: + if (ssi_np) + of_node_put(ssi_np); + if (fm_np) + of_node_put(fm_np); + + return ret; +} + +static int imx_si476x_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_card_imx_3stack; + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id imx_si476x_dt_ids[] = { + { .compatible = "fsl,imx-audio-si476x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_si476x_dt_ids); + +static struct platform_driver imx_si476x_driver = { + .driver = { + .name = "imx-tuner-si476x", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_si476x_dt_ids, + }, + .probe = imx_si476x_probe, + .remove = imx_si476x_remove, +}; + +module_platform_driver(imx_si476x_driver); + +/* Module information */ +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ALSA SoC i.MX si476x"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-tuner-si476x"); diff --git a/sound/soc/fsl/imx-sii902x.c b/sound/soc/fsl/imx-sii902x.c new file mode 100644 index 000000000000..d4b18e0fdf06 --- /dev/null +++ b/sound/soc/fsl/imx-sii902x.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include "fsl_sai.h" + +#define SUPPORT_RATE_NUM 10 + +struct imx_sii902x_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + bool is_stream_opened[2]; +}; + +static int imx_sii902x_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + static u32 support_rates[SUPPORT_RATE_NUM]; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card); + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret; + + data->is_stream_opened[tx] = true; + if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] || + data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) { + data->is_stream_opened[tx] = false; + return -EBUSY; + } + + support_rates[0] = 32000; + support_rates[1] = 48000; + support_rates[2] = 96000; + support_rates[3] = 192000; + constraint_rates.list = support_rates; + constraint_rates.count = 4; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, + 1, 2); + if (ret) + return ret; + + return 0; +} + +static int imx_sii902x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + int ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 24); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return 0; +} + +static void imx_sii902x_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + data->is_stream_opened[tx] = false; +} + +static struct snd_soc_ops imx_sii902x_ops = { + .startup = imx_sii902x_startup, + .shutdown = imx_sii902x_shutdown, + .hw_params = imx_sii902x_hw_params, +}; + +static int imx_sii902x_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *sii902x_np = NULL; + struct platform_device *cpu_pdev; + struct imx_sii902x_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "sii902x hdmi"; + data->dai.stream_name = "sii902x hdmi"; + data->dai.codecs->dai_name = "i2s-hifi"; + data->dai.codecs->name = "hdmi-audio-codec.1"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.ops = &imx_sii902x_ops; + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (sii902x_np) + of_node_put(sii902x_np); + return ret; +} + +static const struct of_device_id imx_sii902x_dt_ids[] = { + { .compatible = "fsl,imx-audio-sii902x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_sii902x_dt_ids); + +static struct platform_driver imx_sii902x_driver = { + .driver = { + .name = "imx-sii902x", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_sii902x_dt_ids, + }, + .probe = imx_sii902x_probe, +}; +module_platform_driver(imx_sii902x_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX SII902X hdmi audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-sii902x"); diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c index 6c4dadf60355..1a64302aa595 100644 --- a/sound/soc/fsl/imx-spdif.c +++ b/sound/soc/fsl/imx-spdif.c @@ -1,16 +1,44 @@ // SPDX-License-Identifier: GPL-2.0+ // -// Copyright (C) 2013 Freescale Semiconductor, Inc. +// Copyright (C) 2013-2019 Freescale Semiconductor, Inc. #include <linux/module.h> #include <linux/of_platform.h> #include <sound/soc.h> +#include "fsl_spdif.h" struct imx_spdif_data { struct snd_soc_dai_link dai; struct snd_soc_card card; }; +#define CLK_8K_FREQ 24576000 +#define CLK_11K_FREQ 22579200 + +static int imx_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + int ret = 0; + u64 rate = params_rate(params); + unsigned int freq; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + freq = do_div(rate, 8000) ? CLK_11K_FREQ : CLK_8K_FREQ; + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, STC_TXCLK_SPDIF_ROOT, + freq, SND_SOC_CLOCK_OUT); + if (ret) + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + } + + return ret; +} + +static struct snd_soc_ops imx_spdif_ops = { + .hw_params = imx_spdif_hw_params, +}; + static int imx_spdif_audio_probe(struct platform_device *pdev) { struct device_node *spdif_np, *np = pdev->dev.of_node; @@ -48,6 +76,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) data->dai.platforms->of_node = spdif_np; data->dai.playback_only = true; data->dai.capture_only = true; + data->dai.ops = &imx_spdif_ops; if (of_property_read_bool(np, "spdif-out")) data->dai.capture_only = false; diff --git a/sound/soc/fsl/imx-ssi.c b/sound/soc/fsl/imx-ssi.c index 42031ba7da31..9038b61317be 100644 --- a/sound/soc/fsl/imx-ssi.c +++ b/sound/soc/fsl/imx-ssi.c @@ -520,8 +520,10 @@ static int imx_ssi_probe(struct platform_device *pdev) } ssi->irq = platform_get_irq(pdev, 0); - if (ssi->irq < 0) + if (ssi->irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ: %d\n", ssi->irq); return ssi->irq; + } ssi->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(ssi->clk)) { diff --git a/sound/soc/fsl/imx-wm8524.c b/sound/soc/fsl/imx-wm8524.c new file mode 100644 index 000000000000..f28e92ca64ba --- /dev/null +++ b/sound/soc/fsl/imx-wm8524.c @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/syscon.h> + +struct imx_priv { + struct snd_soc_dai_link dai[3]; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + struct snd_soc_card card; + struct clk *codec_clk; + unsigned int clk_frequency; + u32 asrc_rate; + u32 asrc_format; +}; + +static const struct snd_soc_dapm_widget imx_wm8524_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static int imx_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + unsigned int fmt; + int ret = 0; + + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, + params_physical_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return ret; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Playback", NULL, "CPU-Playback"}, + {"CPU-Playback", NULL, "ASRC-Playback"}, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = priv->asrc_rate; + rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +static int imx_wm8524_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); + int ret; + + priv->clk_frequency = clk_get_rate(priv->codec_clk); + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, priv->clk_frequency, + SND_SOC_CLOCK_IN); + + return 0; +} + +static int imx_wm8524_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np = NULL; + struct device_node *asrc_np = NULL; + struct platform_device *asrc_pdev = NULL; + struct platform_device *cpu_pdev; + struct imx_priv *priv; + struct platform_device *codec_pdev = NULL; + struct snd_soc_dai_link_component *dlc; + int ret; + u32 width; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + + dlc = devm_kzalloc(&pdev->dev, 9 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_pdev = of_find_device_by_node(codec_np); + if (!codec_pdev || !codec_pdev->dev.driver) { + dev_dbg(&pdev->dev, "failed to find codec platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + priv->codec_clk = devm_clk_get(&codec_pdev->dev, "mclk"); + if (IS_ERR(priv->codec_clk)) { + ret = PTR_ERR(priv->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + priv->dai[0].cpus = &dlc[0]; + priv->dai[0].num_cpus = 1; + priv->dai[0].platforms = &dlc[1]; + priv->dai[0].num_platforms = 1; + priv->dai[0].codecs = &dlc[2]; + priv->dai[0].num_codecs = 1; + + priv->dai[0].name = "HiFi"; + priv->dai[0].stream_name = "HiFi"; + priv->dai[0].codecs->dai_name = "wm8524-hifi", + priv->dai[0].ops = &imx_hifi_ops, + priv->dai[0].codecs->of_node = codec_np; + priv->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + priv->dai[0].platforms->of_node = cpu_np; + priv->dai[0].playback_only = 1; + + priv->card.late_probe = imx_wm8524_late_probe; + priv->card.num_links = 1; + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + priv->card.dapm_widgets = imx_wm8524_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8524_dapm_widgets); + priv->card.dai_link = priv->dai; + priv->card.dapm_routes = audio_map; + priv->card.num_dapm_routes = 1; + + /*if there is no asrc controller, we only enable one device*/ + if (asrc_pdev) { + priv->dai[1].cpus = &dlc[3]; + priv->dai[1].num_cpus = 1; + priv->dai[1].platforms = &dlc[4]; + priv->dai[1].num_platforms = 1; + priv->dai[1].codecs = &dlc[5]; + priv->dai[1].num_codecs = 1; + + priv->dai[2].cpus = &dlc[6]; + priv->dai[2].num_cpus = 1; + priv->dai[2].platforms = &dlc[7]; + priv->dai[2].num_platforms = 1; + priv->dai[2].codecs = &dlc[8]; + priv->dai[2].num_codecs = 1; + + priv->dai[1].name = "HiFi-ASRC-FE"; + priv->dai[1].stream_name = "HiFi-ASRC-FE"; + priv->dai[1].codecs->dai_name = "snd-soc-dummy-dai"; + priv->dai[1].codecs->name = "snd-soc-dummy"; + priv->dai[1].cpus->of_node = asrc_np; + priv->dai[1].platforms->of_node = asrc_np; + priv->dai[1].dynamic = 1; + priv->dai[1].dpcm_playback = 1; + priv->dai[1].dpcm_capture = 0; + + priv->dai[2].name = "HiFi-ASRC-BE"; + priv->dai[2].stream_name = "HiFi-ASRC-BE"; + priv->dai[2].codecs->dai_name = "wm8524-hifi"; + priv->dai[2].codecs->of_node = codec_np; + priv->dai[2].cpus->of_node = cpu_np; + priv->dai[2].platforms->name = "snd-soc-dummy"; + priv->dai[2].no_pcm = 1; + priv->dai[2].dpcm_playback = 1; + priv->dai[2].dpcm_capture = 0; + priv->dai[2].ops = &imx_hifi_ops, + priv->dai[2].be_hw_params_fixup = be_hw_params_fixup, + priv->card.num_links = 3; + priv->card.dai_link = &priv->dai[0]; + priv->card.num_dapm_routes += 1; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) + goto fail; + + ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing"); + if (ret) + goto fail; + + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static const struct of_device_id imx_wm8524_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8524", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8524_dt_ids); + +static struct platform_driver imx_wm8524_driver = { + .driver = { + .name = "imx-wm8524", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8524_dt_ids, + }, + .probe = imx_wm8524_probe, +}; +module_platform_driver(imx_wm8524_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8524 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8524"); diff --git a/sound/soc/fsl/imx-wm8958.c b/sound/soc/fsl/imx-wm8958.c new file mode 100644 index 000000000000..2ffbd1f69e2b --- /dev/null +++ b/sound/soc/fsl/imx-wm8958.c @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/syscon.h> +#include "../fsl/fsl_sai.h" +#include "../codecs/wm8994.h" + +#define DAI_NAME_SIZE 32 + +struct imx_wm8958_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *mclk; + unsigned int clk_frequency; + bool is_codec_master; + int sr_stream[2]; + struct regmap *gpr; +}; + +struct imx_priv { + int hp_gpio; + int hp_active_low; + struct snd_soc_component *component; + struct platform_device *pdev; +}; + +static struct imx_priv card_priv; + +static struct snd_soc_jack imx_hp_jack; + +static struct snd_soc_jack_pin imx_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio imx_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 250, + .invert = 1, +}; + +static int hpjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int hp_status, ret; + + if (!gpio_is_valid(priv->hp_gpio)) + return 0; + + hp_status = gpio_get_value(priv->hp_gpio); + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (hp_status != priv->hp_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk"); + ret = imx_hp_jack_gpio.report; + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk"); + ret = 0; + } + + envp[0] = "NAME=headphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + +static const struct snd_soc_dapm_widget imx_wm8958_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route imx_wm8958_dapm_route[] = { + {"Headphone Jack", NULL, "HPOUT1L"}, + {"Headphone Jack", NULL, "HPOUT1R"}, + {"Ext Spk", NULL, "SPKOUTLP"}, + {"Ext Spk", NULL, "SPKOUTLN"}, + {"Ext Spk", NULL, "SPKOUTRP"}, + {"Ext Spk", NULL, "SPKOUTRN"}, + {"IN1LN", NULL, "MICBIAS2"}, +}; + +static int imx_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int sample_rate = params_rate(params); + unsigned int pll_out; + int ret; + + if (tx && params_width(params) == 24) { + if (sample_rate == 88200 || sample_rate == 96000 || + sample_rate == 48000 || sample_rate == 44100) { + dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate); + return -EINVAL; + } + } else if (!tx && params_width(params) == 24) { + if (sample_rate == 44100 || sample_rate == 48000) { + dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate); + return -EINVAL; + } + } + + ret = snd_soc_dai_set_fmt(codec_dai, data->dai.dai_fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, data->dai.dai_fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + data->clk_frequency = clk_get_rate(data->mclk); + + if (!data->is_codec_master) { + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } else { + data->sr_stream[tx] = sample_rate; + + if (params_width(params) == 24) + pll_out = data->sr_stream[tx] * 384; + else + pll_out = data->sr_stream[tx] * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, + data->clk_frequency, + pll_out); + if (ret) { + dev_err(dev, "failed to set codec pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + } + + /* + * Set GPIO1 pin function to reserve, so that DAC1 and ADC1 using shared + * LRCLK from DACLRCK1. + */ + snd_soc_component_update_bits(codec_dai->component, WM8994_GPIO_1, 0x1f, 0x2); + + /* + * Clear ADC_OSR128 bit to support slower SYSCLK, and support ADC sample + * rate 8K, 11.025K and 12K. + */ + snd_soc_component_update_bits(codec_dai->component, WM8994_OVERSAMPLING, 1<<1, 0); + return 0; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + if (data->is_codec_master && + data->sr_stream[!tx] == 0 && data->sr_stream[tx]) { + /* + * We should connect AIF1CLK source to FLL after enable FLL, and + * disconnet AIF1CLK source to FLL before disable FLL, otherwise + * FLL worked abnormal. + */ + snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1, + data->clk_frequency, SND_SOC_CLOCK_OUT); + + /* Disable FLL1 after all stream finished. */ + snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, 0, 0); + } + + data->sr_stream[tx] = 0; + + return 0; +} + +static u32 imx_wm8958_adc_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000 +}; + +static u32 imx_wm8958_dac_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 88200, 96000 +}; + +static struct snd_pcm_hw_constraint_list imx_wm8958_adc_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8958_adc_rates), + .list = imx_wm8958_adc_rates, +}; + +static struct snd_pcm_hw_constraint_list imx_wm8958_dac_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8958_dac_rates), + .list = imx_wm8958_dac_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0; + + if (!tx) + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_adc_rate_constraints); + else + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_dac_rate_constraints); + return ret; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, + .startup = imx_hifi_startup, +}; + +static int imx_wm8958_gpio_init(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + int ret; + + priv->component = codec_dai->component; + + if (gpio_is_valid(priv->hp_gpio)) { + imx_hp_jack_gpio.gpio = priv->hp_gpio; + imx_hp_jack_gpio.jack_status_check = hpjack_status_check; + + ret = snd_soc_card_jack_new(card, "Headphone Jack", + SND_JACK_HEADPHONE, &imx_hp_jack, + imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins)); + if (ret) + return ret; + + ret = snd_soc_jack_add_gpios(&imx_hp_jack, 1, + &imx_hp_jack_gpio); + if (ret) + return ret; + } + + return 0; +} + +static ssize_t headphone_show(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int hp_status; + + if (!gpio_is_valid(priv->hp_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value(priv->hp_gpio); + + if (hp_status != priv->hp_active_low) + strcpy(buf, "headphone\n"); + else + strcpy(buf, "speaker\n"); + + return strlen(buf); +} + +static DRIVER_ATTR_RO(headphone); + +static int imx_wm8958_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + int ret; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + if (card->dapm.bias_level == SND_SOC_BIAS_OFF) { + if (!IS_ERR(data->mclk)) { + ret = clk_prepare_enable(data->mclk); + if (ret) { + dev_err(card->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + } + break; + default: + break; + } + + return 0; +} + +static int imx_wm8958_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card); + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_OFF: + if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) + if (!IS_ERR(data->mclk)) + clk_disable_unprepare(data->mclk); + break; + default: + break; + } + + card->dapm.bias_level = level; + + return 0; +} + +static int of_parse_gpr(struct platform_device *pdev, + struct imx_wm8958_data *data) +{ + int ret; + struct of_phandle_args args; + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx7d-12x12-lpddr3-arm2-wm8958")) + return 0; + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "gpr", 3, 0, &args); + if (ret) { + dev_warn(&pdev->dev, "failed to get gpr property\n"); + return ret; + } + + data->gpr = syscon_node_to_regmap(args.np); + if (IS_ERR(data->gpr)) { + ret = PTR_ERR(data->gpr); + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + return ret; + } + + regmap_update_bits(data->gpr, args.args[0], args.args[1], + args.args[2]); + + return 0; +} + +static int imx_wm8958_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np = NULL; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev; + struct imx_priv *priv = &card_priv; + struct i2c_client *codec_dev; + struct imx_wm8958_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + + priv->pdev = pdev; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(np, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + ret = of_parse_gpr(pdev, data); + if (ret) + goto fail; + + if (of_property_read_bool(np, "codec-master")) { + data->dai.dai_fmt = SND_SOC_DAIFMT_CBM_CFM; + data->is_codec_master = true; + } else + data->dai.dai_fmt = SND_SOC_DAIFMT_CBS_CFS; + + data->mclk = devm_clk_get(&codec_dev->dev, "mclk1"); + if (IS_ERR(data->mclk)) { + ret = PTR_ERR(data->mclk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0, + (enum of_gpio_flags *)&priv->hp_active_low); + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "HiFi"; + data->dai.stream_name = "HiFi"; + data->dai.codecs->dai_name = "wm8994-aif1"; + data->dai.codecs->name = "wm8994-codec"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.ops = &imx_hifi_ops; + data->dai.dai_fmt |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + data->card.set_bias_level = imx_wm8958_set_bias_level; + data->card.set_bias_level_post = imx_wm8958_set_bias_level_post; + data->card.owner = THIS_MODULE; + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.dapm_widgets = imx_wm8958_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8958_dapm_widgets); + data->card.dapm_routes = imx_wm8958_dapm_route; + data->card.num_dapm_routes = ARRAY_SIZE(imx_wm8958_dapm_route); + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + ret = imx_wm8958_gpio_init(&data->card); + + if (gpio_is_valid(priv->hp_gpio)) { + ret = driver_create_file(pdev->dev.driver, + &driver_attr_headphone); + if (ret) { + dev_err(&pdev->dev, + "create hp attr failed (%d)\n", ret); + goto fail; + } + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8958_remove(struct platform_device *pdev) +{ + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + return 0; +} + +static const struct of_device_id imx_wm8958_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8958", }, + { .compatible = "fsl,imx7d-12x12-lpddr3-arm2-wm8958", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8958_dt_ids); + +static struct platform_driver imx_wm8958_driver = { + .driver = { + .name = "imx-wm8958", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8958_dt_ids, + }, + .probe = imx_wm8958_probe, + .remove = imx_wm8958_remove, +}; +module_platform_driver(imx_wm8958_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8958 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8958"); diff --git a/sound/soc/fsl/imx-wm8960.c b/sound/soc/fsl/imx-wm8960.c new file mode 100644 index 000000000000..5e9b0dd9ed96 --- /dev/null +++ b/sound/soc/fsl/imx-wm8960.c @@ -0,0 +1,732 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> +#include <linux/mfd/syscon.h> +#include "../codecs/wm8960.h" +#include "fsl_sai.h" + +struct imx_wm8960_data { + enum of_gpio_flags hp_active_low; + enum of_gpio_flags mic_active_low; + bool is_headset_jack; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + struct snd_soc_card card; + struct clk *codec_clk; + unsigned int clk_frequency; + bool is_codec_master; + bool is_codec_rpmsg; + bool is_playback_only; + bool is_capture_only; + bool is_stream_in_use[2]; + bool is_stream_opened[2]; + struct regmap *gpr; + unsigned int hp_det[2]; + u32 asrc_rate; + u32 asrc_format; + struct snd_soc_jack imx_hp_jack; + struct snd_soc_jack_pin imx_hp_jack_pin; + struct snd_soc_jack_gpio imx_hp_jack_gpio; + struct snd_soc_jack imx_mic_jack; + struct snd_soc_jack_pin imx_mic_jack_pin; + struct snd_soc_jack_gpio imx_mic_jack_gpio; + struct snd_soc_dai_link imx_wm8960_dai[3]; +}; + +static int hp_jack_status_check(void *data) +{ + struct imx_wm8960_data *imx_data = (struct imx_wm8960_data *)data; + struct snd_soc_jack *jack = &imx_data->imx_hp_jack; + struct snd_soc_dapm_context *dapm = &jack->card->dapm; + int hp_status, ret; + + hp_status = gpio_get_value_cansleep(imx_data->imx_hp_jack_gpio.gpio); + + if (hp_status != imx_data->hp_active_low) { + snd_soc_dapm_disable_pin(dapm, "Ext Spk"); + if (imx_data->is_headset_jack) { + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin(dapm, "Main MIC"); + } + ret = imx_data->imx_hp_jack_gpio.report; + } else { + snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + if (imx_data->is_headset_jack) { + snd_soc_dapm_disable_pin(dapm, "Mic Jack"); + snd_soc_dapm_enable_pin(dapm, "Main MIC"); + } + ret = 0; + } + + return ret; +} + +static int mic_jack_status_check(void *data) +{ + struct imx_wm8960_data *imx_data = (struct imx_wm8960_data *)data; + struct snd_soc_jack *jack = &imx_data->imx_mic_jack; + struct snd_soc_dapm_context *dapm = &jack->card->dapm; + int mic_status, ret; + + mic_status = gpio_get_value_cansleep(imx_data->imx_mic_jack_gpio.gpio); + + if (mic_status != imx_data->mic_active_low) { + snd_soc_dapm_disable_pin(dapm, "Main MIC"); + ret = imx_data->imx_mic_jack_gpio.report; + } else { + snd_soc_dapm_enable_pin(dapm, "Main MIC"); + ret = 0; + } + + return ret; +} + +static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Main MIC", NULL), +}; + +static int imx_wm8960_jack_init(struct snd_soc_card *card, + struct snd_soc_jack *jack, struct snd_soc_jack_pin *pin, + struct snd_soc_jack_gpio *gpio) +{ + int ret; + + ret = snd_soc_card_jack_new(card, pin->pin, pin->mask, jack, pin, 1); + if (ret) { + return ret; + } + + ret = snd_soc_jack_add_gpios(jack, 1, gpio); + if (ret) + return ret; + + return 0; +} + +static ssize_t headphone_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_card *card = dev_get_drvdata(dev); + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + int hp_status; + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value_cansleep(data->imx_hp_jack_gpio.gpio); + + if (hp_status != data->hp_active_low) + strcpy(buf, "Headphone\n"); + else + strcpy(buf, "Speaker\n"); + + return strlen(buf); +} + +static ssize_t micphone_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_card *card = dev_get_drvdata(dev); + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + int mic_status; + + /* Check if headphone is plugged in */ + mic_status = gpio_get_value_cansleep(data->imx_mic_jack_gpio.gpio); + + if (mic_status != data->mic_active_low) + strcpy(buf, "Mic Jack\n"); + else + strcpy(buf, "Main MIC\n"); + + return strlen(buf); +} +static DEVICE_ATTR_RO(headphone); +static DEVICE_ATTR_RO(micphone); + +static int imx_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = card->dev; + unsigned int sample_rate = params_rate(params); + unsigned int pll_out; + unsigned int fmt; + int ret = 0; + + data->is_stream_in_use[tx] = true; + + if (data->is_stream_in_use[!tx]) + return 0; + + if (data->is_codec_master) + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (!data->is_codec_master) { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + return 0; + } else { + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + /* Set codec pll */ + if (params_width(params) == 24) + pll_out = sample_rate * 768; + else + pll_out = sample_rate * 512; + + ret = snd_soc_dai_set_pll(codec_dai, WM8960_SYSCLK_AUTO, 0, data->clk_frequency, pll_out); + if (ret) + return ret; + ret = snd_soc_dai_set_sysclk(codec_dai, WM8960_SYSCLK_AUTO, pll_out, 0); + + return ret; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = card->dev; + int ret; + + data->is_stream_in_use[tx] = false; + + if (data->is_codec_master && !data->is_stream_in_use[!tx]) { + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF); + if (ret) + dev_warn(dev, "failed to set codec dai fmt: %d\n", ret); + } + + return 0; +} + +static u32 imx_wm8960_rates[] = { 8000, 16000, 32000, 48000 }; +static struct snd_pcm_hw_constraint_list imx_wm8960_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8960_rates), + .list = imx_wm8960_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + int ret = 0; + + data->is_stream_opened[tx] = true; + if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] || + data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) { + data->is_stream_opened[tx] = false; + return -EBUSY; + } + + if (!data->is_codec_master) { + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8960_rate_constraints); + if (ret) + return ret; + } + + return ret; +} + +static void imx_hifi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + data->is_stream_opened[tx] = false; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, + .startup = imx_hifi_startup, + .shutdown = imx_hifi_shutdown, +}; + +static int imx_wm8960_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + + /* + * codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame + * clock for ADCs and DACs + */ + snd_soc_component_update_bits(codec_dai->component, WM8960_IFACE2, 1<<6, 1<<6); + + /* GPIO1 used as headphone detect output */ + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL4, 7<<4, 3<<4); + + /* Enable headphone jack detect */ + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL2, 1<<6, 1<<6); + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5); + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2); + snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL1, 3, 3); + + return 0; +} + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card); + struct snd_interval *rate; + struct snd_mask *mask; + + if (!data->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = data->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, data->asrc_format); + + return 0; +} + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static int of_parse_gpr(struct platform_device *pdev, + struct imx_wm8960_data *data) +{ + int ret; + struct of_phandle_args args; + + if (of_device_is_compatible(pdev->dev.of_node, + "fsl,imx7d-evk-wm8960")) + return 0; + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "gpr", 3, 0, &args); + if (ret) { + dev_warn(&pdev->dev, "failed to get gpr property\n"); + return ret; + } + + data->gpr = syscon_node_to_regmap(args.np); + if (IS_ERR(data->gpr)) { + ret = PTR_ERR(data->gpr); + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + return ret; + } + + regmap_update_bits(data->gpr, args.args[0], args.args[1], + args.args[2]); + + return 0; +} + +static int imx_wm8960_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np = NULL, *codec_np = NULL; + struct platform_device *cpu_pdev; + struct imx_wm8960_data *data; + struct platform_device *asrc_pdev = NULL; + struct device_node *asrc_np; + u32 width; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->pdev = pdev; + data->imx_hp_jack_pin.pin = "Headphone Jack"; + data->imx_hp_jack_pin.mask = SND_JACK_HEADPHONE; + + data->imx_hp_jack_gpio.name = "headphone detect"; + data->imx_hp_jack_gpio.report = SND_JACK_HEADPHONE; + data->imx_hp_jack_gpio.debounce_time = 250; + data->imx_hp_jack_gpio.invert = 0; + + data->imx_mic_jack_pin.pin = "Mic Jack"; + data->imx_mic_jack_pin.mask = SND_JACK_MICROPHONE; + + data->imx_mic_jack_gpio.name = "mic detect"; + data->imx_mic_jack_gpio.report = SND_JACK_MICROPHONE; + data->imx_mic_jack_gpio.debounce_time = 250; + data->imx_mic_jack_gpio.invert = 0; + + if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg")) + data->is_codec_rpmsg = true; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + if (data->is_codec_rpmsg) { + struct platform_device *codec_dev; + + codec_dev = of_find_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } else { + struct i2c_client *codec_dev; + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_dbg(&pdev->dev, "failed to find codec platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + data->is_codec_master = true; + + if (of_property_read_bool(pdev->dev.of_node, "capture-only")) + data->is_capture_only = true; + + if (of_property_read_bool(pdev->dev.of_node, "playback-only")) + data->is_playback_only = true; + + if (data->is_capture_only && data->is_playback_only) { + ret = -EINVAL; + dev_err(&pdev->dev, "failed for playback only and capture only\n"); + goto fail; + } + + data->imx_wm8960_dai[0].name = "HiFi"; + data->imx_wm8960_dai[0].stream_name = "HiFi"; + data->imx_wm8960_dai[0].ops = &imx_hifi_ops; + data->imx_wm8960_dai[0].cpus = hifi_cpus; + data->imx_wm8960_dai[0].num_cpus = ARRAY_SIZE(hifi_cpus); + data->imx_wm8960_dai[0].codecs = hifi_codecs; + data->imx_wm8960_dai[0].num_codecs = ARRAY_SIZE(hifi_codecs); + data->imx_wm8960_dai[0].platforms = hifi_platforms; + data->imx_wm8960_dai[0].num_platforms = ARRAY_SIZE(hifi_platforms); + + data->imx_wm8960_dai[1].name = "HiFi-ASRC-FE"; + data->imx_wm8960_dai[1].stream_name = "HiFi-ASRC-FE"; + data->imx_wm8960_dai[1].dynamic = 1; + data->imx_wm8960_dai[1].ignore_pmdown_time = 1; + data->imx_wm8960_dai[1].dpcm_playback = 1; + data->imx_wm8960_dai[1].dpcm_capture = 1; + data->imx_wm8960_dai[1].dpcm_merged_chan = 1; + data->imx_wm8960_dai[1].cpus = hifi_fe_cpus; + data->imx_wm8960_dai[1].num_cpus = ARRAY_SIZE(hifi_fe_cpus); + data->imx_wm8960_dai[1].codecs = hifi_fe_codecs; + data->imx_wm8960_dai[1].num_codecs = ARRAY_SIZE(hifi_fe_codecs); + data->imx_wm8960_dai[1].platforms = hifi_fe_platforms; + data->imx_wm8960_dai[1].num_platforms = ARRAY_SIZE(hifi_fe_platforms); + + data->imx_wm8960_dai[2].name = "HiFi-ASRC-BE"; + data->imx_wm8960_dai[2].stream_name = "HiFi-ASRC-BE"; + data->imx_wm8960_dai[2].no_pcm = 1; + data->imx_wm8960_dai[2].ignore_pmdown_time = 1; + data->imx_wm8960_dai[2].dpcm_playback = 1; + data->imx_wm8960_dai[2].dpcm_capture = 1; + data->imx_wm8960_dai[2].ops = &imx_hifi_ops; + data->imx_wm8960_dai[2].be_hw_params_fixup = be_hw_params_fixup; + data->imx_wm8960_dai[2].cpus = hifi_be_cpus; + data->imx_wm8960_dai[2].num_cpus = ARRAY_SIZE(hifi_be_cpus); + data->imx_wm8960_dai[2].codecs = hifi_be_codecs; + data->imx_wm8960_dai[2].num_codecs = ARRAY_SIZE(hifi_be_codecs); + data->imx_wm8960_dai[2].platforms = hifi_be_platforms; + data->imx_wm8960_dai[2].num_platforms = ARRAY_SIZE(hifi_be_platforms); + + if (data->is_capture_only) { + data->imx_wm8960_dai[0].capture_only = true; + data->imx_wm8960_dai[1].capture_only = true; + data->imx_wm8960_dai[2].capture_only = true; + } + + if (data->is_playback_only) { + data->imx_wm8960_dai[0].playback_only = true; + data->imx_wm8960_dai[1].playback_only = true; + data->imx_wm8960_dai[2].playback_only = true; + } + + ret = of_parse_gpr(pdev, data); + if (ret) + goto fail; + + of_property_read_u32_array(pdev->dev.of_node, "hp-det", data->hp_det, 2); + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + data->asrc_pdev = asrc_pdev; + } + + data->card.dai_link = data->imx_wm8960_dai; + + if (data->is_codec_rpmsg) { + data->imx_wm8960_dai[0].codecs->name = "rpmsg-audio-codec-wm8960"; + data->imx_wm8960_dai[0].codecs->dai_name = "rpmsg-wm8960-hifi"; + } else + data->imx_wm8960_dai[0].codecs->of_node = codec_np; + + data->imx_wm8960_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->imx_wm8960_dai[0].platforms->of_node = cpu_np; + + if (!asrc_pdev) { + data->card.num_links = 1; + } else { + data->imx_wm8960_dai[1].cpus->of_node = asrc_np; + data->imx_wm8960_dai[1].platforms->of_node = asrc_np; + if (data->is_codec_rpmsg) { + data->imx_wm8960_dai[2].codecs->name = "rpmsg-audio-codec-wm8960"; + data->imx_wm8960_dai[2].codecs->dai_name = "rpmsg-wm8960-hifi"; + } else + data->imx_wm8960_dai[2].codecs->of_node = codec_np; + data->imx_wm8960_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &data->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + data->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + data->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.dapm_widgets = imx_wm8960_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets); + + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + + data->card.late_probe = imx_wm8960_late_probe; + + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + data->imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node, + "hp-det-gpios", 0, + &data->hp_active_low); + + data->imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node, + "mic-det-gpios", 0, + &data->mic_active_low); + + if (gpio_is_valid(data->imx_hp_jack_gpio.gpio) && + gpio_is_valid(data->imx_mic_jack_gpio.gpio) && + data->imx_hp_jack_gpio.gpio == data->imx_mic_jack_gpio.gpio) + data->is_headset_jack = true; + + if (gpio_is_valid(data->imx_hp_jack_gpio.gpio)) { + if (data->is_headset_jack) { + data->imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE; + data->imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE; + } + + data->imx_hp_jack_gpio.jack_status_check = hp_jack_status_check; + data->imx_hp_jack_gpio.data = data; + ret = imx_wm8960_jack_init(&data->card, &data->imx_hp_jack, + &data->imx_hp_jack_pin, &data->imx_hp_jack_gpio); + if (ret) { + dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret); + goto out; + } + + ret = device_create_file(&pdev->dev, &dev_attr_headphone); + if (ret) + dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret); + } + + if (gpio_is_valid(data->imx_mic_jack_gpio.gpio)) { + if (!data->is_headset_jack) { + data->imx_mic_jack_gpio.jack_status_check = mic_jack_status_check; + data->imx_mic_jack_gpio.data = data; + ret = imx_wm8960_jack_init(&data->card, &data->imx_mic_jack, + &data->imx_mic_jack_pin, &data->imx_mic_jack_gpio); + if (ret) { + dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret); + goto out; + } + } + + ret = device_create_file(&pdev->dev, &dev_attr_micphone); + if (ret) + dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret); + } + +out: + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8960_remove(struct platform_device *pdev) +{ + device_remove_file(&pdev->dev, &dev_attr_micphone); + device_remove_file(&pdev->dev, &dev_attr_headphone); + + return 0; +} + +static const struct of_device_id imx_wm8960_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8960", }, + { .compatible = "fsl,imx7d-evk-wm8960" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids); + +static struct platform_driver imx_wm8960_driver = { + .driver = { + .name = "imx-wm8960", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8960_dt_ids, + }, + .probe = imx_wm8960_probe, + .remove = imx_wm8960_remove, +}; +module_platform_driver(imx_wm8960_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8960 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8960"); diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c new file mode 100644 index 000000000000..cb50a6fa48a9 --- /dev/null +++ b/sound/soc/fsl/imx-wm8962.c @@ -0,0 +1,860 @@ +/* + * Copyright (C) 2013-2016 Freescale Semiconductor, Inc. + * + * Based on imx-sgtl5000.c + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Linaro Ltd. + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/control.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> + +#include "../codecs/wm8962.h" +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 + +struct imx_wm8962_data { + struct snd_soc_dai_link dai[3]; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *codec_clk; + unsigned int clk_frequency; + bool is_codec_master; +}; + +struct imx_priv { + int hp_gpio; + int hp_active_low; + int mic_gpio; + int mic_active_low; + bool amic_mono; + bool dmic_mono; + struct snd_soc_component *component; + struct platform_device *pdev; + struct snd_pcm_substream *first_stream; + struct snd_pcm_substream *second_stream; + struct platform_device *asrc_pdev; + u32 asrc_rate; + u32 asrc_format; +}; +static struct imx_priv card_priv; + +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID +static int sample_rate = 44100; +static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE; +#endif + +static struct snd_soc_jack imx_hp_jack; +static struct snd_soc_jack_pin imx_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; +static struct snd_soc_jack_gpio imx_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static struct snd_soc_jack imx_mic_jack; +static struct snd_soc_jack_pin imx_mic_jack_pins[] = { + { + .pin = "AMIC", + .mask = SND_JACK_MICROPHONE, + }, +}; +static struct snd_soc_jack_gpio imx_mic_jack_gpio = { + .name = "microphone detect", + .report = SND_JACK_MICROPHONE, + .debounce_time = 250, + .invert = 0, +}; + +static int hpjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int hp_status, ret; + + if (!gpio_is_valid(priv->hp_gpio)) + return 0; + + hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0; + + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (hp_status != priv->hp_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk"); + ret = imx_hp_jack_gpio.report; + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk"); + ret = 0; + } + + envp[0] = "NAME=headphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + +static int micjack_status_check(void *data) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + char *envp[3], *buf; + int mic_status, ret; + + if (!gpio_is_valid(priv->mic_gpio)) + return 0; + + mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0; + + if ((mic_status != priv->mic_active_low && priv->amic_mono) + || (mic_status == priv->mic_active_low && priv->dmic_mono)) + snd_soc_component_update_bits(priv->component, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX); + + else + snd_soc_component_update_bits(priv->component, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, 0); + + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + dev_err(&pdev->dev, "%s kmalloc failed\n", __func__); + return -ENOMEM; + } + + if (mic_status != priv->mic_active_low) { + snprintf(buf, 32, "STATE=%d", 2); + snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "DMIC"); + ret = imx_mic_jack_gpio.report; + } else { + snprintf(buf, 32, "STATE=%d", 0); + snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "DMIC"); + ret = 0; + } + + envp[0] = "NAME=microphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + return ret; +} + + +static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_MIC("DMIC", NULL), +}; + +static u32 imx_wm8962_rates[] = {32000, 48000, 96000}; +static struct snd_pcm_hw_constraint_list imx_wm8962_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8962_rates), + .list = imx_wm8962_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + int ret; + + if (!data->is_codec_master) { + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &imx_wm8962_rate_constraints); + if (ret) + return ret; + } + + return 0; +} + +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID +static int imx_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + u32 dai_format; + int ret = 0; + + sample_rate = params_rate(params); + sample_format = params_format(params); + + if (data->is_codec_master) + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_ops imx_hifi_ops = { + .startup = imx_hifi_startup, + .hw_params = imx_hifi_hw_params, +}; + +static int imx_wm8962_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct imx_priv *priv = &card_priv; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + struct device *dev = &priv->pdev->dev; + unsigned int pll_out; + int ret; + + if (dapm->dev != codec_dai->dev) + return 0; + + data->clk_frequency = clk_get_rate(data->codec_clk); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + if (sample_format == SNDRV_PCM_FORMAT_S24_LE + || sample_format == SNDRV_PCM_FORMAT_S20_3LE) + pll_out = sample_rate * 384; + else + pll_out = sample_rate * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + WM8962_FLL_MCLK, data->clk_frequency, + pll_out); + if (ret < 0) { + dev_err(dev, "failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8962_SYSCLK_FLL, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "failed to set SYSCLK: %d\n", ret); + return ret; + } + } + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level == SND_SOC_BIAS_PREPARE) { + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8962_SYSCLK_MCLK, data->clk_frequency, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, + "failed to switch away from FLL: %d\n", + ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + 0, 0, 0); + if (ret < 0) { + dev_err(dev, "failed to stop FLL: %d\n", ret); + return ret; + } + } + break; + + default: + break; + } + + return 0; +} + +#else + +static int imx_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + struct snd_soc_card *card = platform_get_drvdata(priv->pdev); + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + unsigned int sample_rate = params_rate(params); + snd_pcm_format_t sample_format = params_format(params); + u32 dai_format, pll_out; + int ret = 0; + + if (!priv->first_stream) { + priv->first_stream = substream; + } else { + priv->second_stream = substream; + + /* We suppose the two substream are using same params */ + return 0; + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + if (data->is_codec_master) + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (sample_format == SNDRV_PCM_FORMAT_S24_LE + || sample_format == SNDRV_PCM_FORMAT_S20_3LE) + pll_out = sample_rate * 384; + else + pll_out = sample_rate * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, WM8962_FLL_MCLK, + data->clk_frequency, pll_out); + if (ret) { + dev_err(dev, "failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_FLL, + pll_out, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + struct device *dev = &priv->pdev->dev; + int ret; + + /* We don't need to handle anything if there's no substream running */ + if (!priv->first_stream) + return 0; + + if (priv->first_stream == substream) + priv->first_stream = priv->second_stream; + priv->second_stream = NULL; + + if (!priv->first_stream) { + /* + * Continuously setting FLL would cause playback distortion. + * We can fix it just by mute codec after playback. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dai_digital_mute(codec_dai, 1, substream->stream); + + /* + * WM8962 doesn't allow us to continuously setting FLL, + * So we set MCLK as sysclk once, which'd remove the limitation. + */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "failed to switch away from FLL: %d\n", ret); + return ret; + } + + /* Disable FLL and let codec do pm_runtime_put() */ + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + WM8962_FLL_MCLK, 0, 0); + if (ret < 0) { + dev_err(dev, "failed to stop FLL: %d\n", ret); + return ret; + } + } + + return 0; +} + +static struct snd_soc_ops imx_hifi_ops = { + .startup = imx_hifi_startup, + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, +}; +#endif /* CONFIG_SND_SOC_IMX_WM8962_ANDROID */ + +static int imx_wm8962_gpio_init(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_priv *priv = &card_priv; + + priv->component = codec_dai->component; + + if (gpio_is_valid(priv->hp_gpio)) { + imx_hp_jack_gpio.gpio = priv->hp_gpio; + imx_hp_jack_gpio.jack_status_check = hpjack_status_check; + + snd_soc_card_jack_new(card, "Headphone Jack", + SND_JACK_HEADPHONE, &imx_hp_jack, + imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins)); + + snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio); + } + + if (gpio_is_valid(priv->mic_gpio)) { + imx_mic_jack_gpio.gpio = priv->mic_gpio; + imx_mic_jack_gpio.jack_status_check = micjack_status_check; + + snd_soc_card_jack_new(card, "AMIC", + SND_JACK_MICROPHONE, &imx_mic_jack, + imx_mic_jack_pins, ARRAY_SIZE(imx_mic_jack_pins)); + + snd_soc_jack_add_gpios(&imx_mic_jack, 1, &imx_mic_jack_gpio); + } else if (priv->amic_mono || priv->dmic_mono) { + /* + * Permanent set monomix bit if only one microphone + * is present on the board while it needs monomix. + */ + snd_soc_component_update_bits(priv->component, WM8962_THREED1, + WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX); + } + + return 0; +} + +static ssize_t headphone_show(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int hp_status; + + if (!gpio_is_valid(priv->hp_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if headphone is plugged in */ + hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0; + + if (hp_status != priv->hp_active_low) + strcpy(buf, "headphone\n"); + else + strcpy(buf, "speaker\n"); + + return strlen(buf); +} + +static DRIVER_ATTR_RO(headphone); + +static ssize_t microphone_show(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + int mic_status; + + if (!gpio_is_valid(priv->mic_gpio)) { + strcpy(buf, "no detect gpio connected\n"); + return strlen(buf); + } + + /* Check if analog microphone is plugged in */ + mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0; + + if (mic_status != priv->mic_active_low) + strcpy(buf, "amic\n"); + else + strcpy(buf, "dmic\n"); + + return strlen(buf); +} + +static DRIVER_ATTR_RO(microphone); + +static int imx_wm8962_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct imx_priv *priv = &card_priv; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + struct device *dev = &priv->pdev->dev; + int ret; + + data->clk_frequency = clk_get_rate(data->codec_clk); + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + codec_dai = rtd->codec_dai; + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(dev, "failed to set sysclk in %s\n", __func__); + + return ret; +} + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +static int imx_wm8962_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *cpu_np = NULL, *codec_np = NULL; + struct platform_device *cpu_pdev; + struct imx_priv *priv = &card_priv; + struct i2c_client *codec_dev; + struct imx_wm8962_data *data; + int int_port, ext_port, tmp_port; + int ret; + struct platform_device *asrc_pdev = NULL; + struct device_node *asrc_np; + struct snd_soc_dai_link_component *dlc; + u32 width; + + priv->pdev = pdev; + priv->asrc_pdev = NULL; + + dlc = devm_kzalloc(&pdev->dev, 9 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + data->is_codec_master = true; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + if (!strstr(cpu_np->name, "ssi")) + goto audmux_bypass; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + goto fail; + } + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + goto fail; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + if (data->is_codec_master) { + tmp_port = int_port; + int_port = ext_port; + ext_port = tmp_port; + } + + ret = imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(&pdev->dev, "audmux internal port setup failed\n"); + goto fail; + } + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(&pdev->dev, "audmux external port setup failed\n"); + goto fail; + } + +audmux_bypass: + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + priv->first_stream = NULL; + priv->second_stream = NULL; + + data->codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + priv->amic_mono = of_property_read_bool(codec_np, "amic-mono"); + priv->dmic_mono = of_property_read_bool(codec_np, "dmic-mono"); + + priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0, + (enum of_gpio_flags *)&priv->hp_active_low); + priv->mic_gpio = of_get_named_gpio_flags(np, "mic-det-gpios", 0, + (enum of_gpio_flags *)&priv->mic_active_low); + + data->dai[0].cpus = &dlc[0]; + data->dai[0].num_cpus = 1; + data->dai[0].platforms = &dlc[1]; + data->dai[0].num_platforms = 1; + data->dai[0].codecs = &dlc[2]; + data->dai[0].num_codecs = 1; + + data->dai[0].name = "HiFi"; + data->dai[0].stream_name = "HiFi"; + data->dai[0].codecs->dai_name = "wm8962"; + data->dai[0].codecs->of_node = codec_np; + data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].platforms->of_node = cpu_np; + data->dai[0].ops = &imx_hifi_ops; + data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + if (data->is_codec_master) + data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + else + data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + + data->card.num_links = 1; + + if (asrc_pdev) { + data->dai[1].cpus = &dlc[3]; + data->dai[1].num_cpus = 1; + data->dai[1].platforms = &dlc[4]; + data->dai[1].num_platforms = 1; + data->dai[1].codecs = &dlc[5]; + data->dai[1].num_codecs = 1; + + data->dai[2].cpus = &dlc[6]; + data->dai[2].num_cpus = 1; + data->dai[2].platforms = &dlc[7]; + data->dai[2].num_platforms = 1; + data->dai[2].codecs = &dlc[8]; + data->dai[2].num_codecs = 1; + + data->dai[0].ignore_pmdown_time = 1; + data->dai[1].name = "HiFi-ASRC-FE"; + data->dai[1].stream_name = "HiFi-ASRC-FE"; + data->dai[1].codecs->name = "snd-soc-dummy"; + data->dai[1].codecs->dai_name = "snd-soc-dummy-dai"; + data->dai[1].cpus->of_node = asrc_np; + data->dai[1].platforms->of_node = asrc_np; + data->dai[1].dynamic = 1; + data->dai[1].ignore_pmdown_time = 1; + data->dai[1].dpcm_playback = 1; + data->dai[1].dpcm_capture = 1; + data->dai[1].dpcm_merged_chan = 1; + + data->dai[2].name = "HiFi-ASRC-BE"; + data->dai[2].stream_name = "HiFi-ASRC-BE"; + data->dai[2].codecs->dai_name = "wm8962"; + data->dai[2].codecs->of_node = codec_np; + data->dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai[2].platforms->name = "snd-soc-dummy"; + data->dai[2].ops = &imx_hifi_ops; + data->dai[2].be_hw_params_fixup = be_hw_params_fixup; + data->dai[2].no_pcm = 1; + data->dai[2].ignore_pmdown_time = 1; + data->dai[2].dpcm_playback = 1; + data->dai[2].dpcm_capture = 1; + data->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + data->card.owner = THIS_MODULE; + data->card.dai_link = data->dai; + data->card.dapm_widgets = imx_wm8962_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets); + + data->card.late_probe = imx_wm8962_late_probe; + +#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID + data->card.set_bias_level = imx_wm8962_set_bias_level; +#endif + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + imx_wm8962_gpio_init(&data->card); + + if (gpio_is_valid(priv->hp_gpio)) { + ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone); + if (ret) { + dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret); + goto fail_hp; + } + } + + if (gpio_is_valid(priv->mic_gpio)) { + ret = driver_create_file(pdev->dev.driver, &driver_attr_microphone); + if (ret) { + dev_err(&pdev->dev, "create mic attr failed (%d)\n", ret); + goto fail_mic; + } + } + + goto fail; + +fail_mic: + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); +fail_hp: +fail: + of_node_put(cpu_np); + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8962_remove(struct platform_device *pdev) +{ + driver_remove_file(pdev->dev.driver, &driver_attr_microphone); + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + + return 0; +} + +static const struct of_device_id imx_wm8962_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8962", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8962_dt_ids); + +static struct platform_driver imx_wm8962_driver = { + .driver = { + .name = "imx-wm8962", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8962_dt_ids, + }, + .probe = imx_wm8962_probe, + .remove = imx_wm8962_remove, +}; +module_platform_driver(imx_wm8962_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8962 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8962"); diff --git a/sound/soc/fsl/imx-xcvr.c b/sound/soc/fsl/imx-xcvr.c new file mode 100644 index 000000000000..ff9bcb279777 --- /dev/null +++ b/sound/soc/fsl/imx-xcvr.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <sound/soc.h> +#include "fsl_xcvr.h" + +struct imx_xcvr_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; +}; + +static int imx_xcvr_audio_probe(struct platform_device *pdev) +{ + struct device_node *xcvr_np, *np = pdev->dev.of_node; + struct snd_soc_dai_link_component *dlc; + struct imx_xcvr_data *data; + int ret = 0; + + xcvr_np = of_parse_phandle(np, "cpu-dai", 0); + if (!xcvr_np) { + dev_err(&pdev->dev, "failed to find cpu-dai\n"); + ret = -EINVAL; + goto end; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto end; + } + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) { + ret = -ENOMEM; + goto end; + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "XCVR PCM"; + data->dai.stream_name = "XCVR PCM"; + data->dai.codecs->dai_name = "snd-soc-dummy-dai"; + data->dai.codecs->name = "snd-soc-dummy"; + data->dai.cpus->of_node = xcvr_np; + data->dai.platforms->of_node = xcvr_np; + + data->card.dev = &pdev->dev; + data->card.dai_link = &data->dai; + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto end; + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret); + goto end; + } +end: + of_node_put(xcvr_np); + return ret; +} + +static const struct of_device_id imx_xcvr_dt_ids[] = { + { .compatible = "fsl,imx-audio-xcvr", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_xcvr_dt_ids); + +static struct platform_driver imx_xcvr_driver = { + .driver = { + .name = "imx-xcvr", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_xcvr_dt_ids, + }, + .probe = imx_xcvr_audio_probe, +}; + +module_platform_driver(imx_xcvr_driver); + +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_DESCRIPTION("NXP Audio Transceiver (XCVR) machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-xcvr"); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 4484c40c7a39..58c200c27ba1 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -371,6 +371,7 @@ static int simple_for_each_link(struct asoc_simple_priv *priv, do { struct asoc_simple_data adata; struct device_node *codec; + struct device_node *plat; struct device_node *np; int num = of_get_child_count(node); @@ -381,6 +382,9 @@ static int simple_for_each_link(struct asoc_simple_priv *priv, ret = -ENODEV; goto error; } + /* get platform */ + plat = of_get_child_by_name(node, is_top ? + PREFIX "plat" : "plat"); /* get convert-xxx property */ memset(&adata, 0, sizeof(adata)); @@ -389,6 +393,8 @@ static int simple_for_each_link(struct asoc_simple_priv *priv, /* loop for all CPU/Codec node */ for_each_child_of_node(node, np) { + if (plat == np) + continue; /* * It is DPCM * if it has many CPUs, diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c index 2a4ffe945177..4b1170562751 100644 --- a/sound/soc/kirkwood/kirkwood-i2s.c +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -537,8 +537,10 @@ static int kirkwood_i2s_dev_probe(struct platform_device *pdev) return PTR_ERR(priv->io); priv->irq = platform_get_irq(pdev, 0); - if (priv->irq < 0) + if (priv->irq < 0) { + dev_err(&pdev->dev, "platform_get_irq failed: %d\n", priv->irq); return priv->irq; + } if (np) { priv->burst = 128; /* might be 32 or 128 */ diff --git a/sound/soc/mediatek/common/mtk-btcvsd.c b/sound/soc/mediatek/common/mtk-btcvsd.c index b66f7dee1e14..5b47cf5d7ead 100644 --- a/sound/soc/mediatek/common/mtk-btcvsd.c +++ b/sound/soc/mediatek/common/mtk-btcvsd.c @@ -1335,8 +1335,10 @@ static int mtk_btcvsd_snd_probe(struct platform_device *pdev) /* irq */ irq_id = platform_get_irq(pdev, 0); - if (irq_id <= 0) + if (irq_id <= 0) { + dev_err(dev, "%pOFn no irq found\n", dev->of_node); return irq_id < 0 ? irq_id : -ENXIO; + } ret = devm_request_irq(dev, irq_id, mtk_btcvsd_snd_irq_handler, IRQF_TRIGGER_LOW, "BTCVSD_ISR_Handle", diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c index 76502ba261c8..0239f840b096 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -1350,8 +1350,10 @@ static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) return -ENOMEM; irq_id = platform_get_irq_byname(pdev, "asys"); - if (irq_id < 0) + if (irq_id < 0) { + dev_err(dev, "unable to get ASYS IRQ\n"); return irq_id; + } ret = devm_request_irq(dev, irq_id, mt2701_asys_isr, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index 0ee29255e731..90bd2c92cae7 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -1074,8 +1074,10 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) afe->dev = &pdev->dev; irq_id = platform_get_irq(pdev, 0); - if (irq_id <= 0) + if (irq_id <= 0) { + dev_err(afe->dev, "np %pOFn no irq\n", afe->dev->of_node); return irq_id < 0 ? irq_id : -ENXIO; + } ret = devm_request_irq(afe->dev, irq_id, mt8173_afe_irq_handler, 0, "Afe_ISR_Handle", (void *)afe); if (ret) { diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c index 1e38ce858326..a2c79426513b 100644 --- a/sound/soc/mxs/mxs-saif.c +++ b/sound/soc/mxs/mxs-saif.c @@ -790,8 +790,12 @@ static int mxs_saif_probe(struct platform_device *pdev) return PTR_ERR(saif->base); irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + if (irq < 0) { + ret = irq; + dev_err(&pdev->dev, "failed to get irq resource: %d\n", + ret); + return ret; + } saif->dev = &pdev->dev; ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0, diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c index b1981d84ac18..400f29027d83 100644 --- a/sound/soc/qcom/lpass-platform.c +++ b/sound/soc/qcom/lpass-platform.c @@ -568,8 +568,11 @@ int asoc_qcom_lpass_platform_register(struct platform_device *pdev) int ret; drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif"); - if (drvdata->lpaif_irq < 0) + if (drvdata->lpaif_irq < 0) { + dev_err(&pdev->dev, "error getting irq handle: %d\n", + drvdata->lpaif_irq); return -ENODEV; + } /* ensure audio hardware is disabled */ ret = regmap_write(drvdata->lpaif_map, diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index c0e03cc8ea82..7c10b284555c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1892,7 +1892,9 @@ match: dev_err(card->dev, "init platform error"); continue; } - dai_link->platforms->name = component->name; + + if (!dai_link->platforms->of_node) + dai_link->platforms->name = component->name; /* convert non BE into BE */ if (!dai_link->no_pcm) { diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 1196167364d4..63679ec24b3a 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1299,7 +1299,7 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, } /* dai link name and stream name set correctly ? */ - dev_err(card->dev, "ASoC: can't get %s BE for %s\n", + dev_dbg(card->dev, "ASoC: can't get %s BE for %s\n", stream ? "capture" : "playback", widget->name); return NULL; } @@ -1453,7 +1453,7 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, /* is there a valid BE rtd for this widget */ be = dpcm_get_be(card, list->widgets[i], stream); if (!be) { - dev_err(fe->dev, "ASoC: no BE found for %s\n", + dev_dbg(fe->dev, "ASoC: no BE found for %s\n", list->widgets[i]->name); continue; } @@ -2871,6 +2871,26 @@ static void soc_pcm_private_free(struct snd_pcm *pcm) snd_soc_pcm_component_free(pcm); } +static int soc_rtdcom_ack(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->ack) + continue; + + /* FIXME. it returns 1st ask now */ + return component->driver->ops->ack(substream); + } + + return -EINVAL; +} + /* create a new pcm */ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { @@ -2994,6 +3014,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) if (!ops) continue; + if (ops->ack) + rtd->ops.ack = soc_rtdcom_ack; if (ops->copy_user) rtd->ops.copy_user = snd_soc_pcm_component_copy_user; if (ops->page) diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c index 54dcece52b0c..c5f809d3a710 100644 --- a/sound/soc/soc-utils.c +++ b/sound/soc/soc-utils.c @@ -99,6 +99,9 @@ static const struct snd_soc_component_driver dummy_codec = { SNDRV_PCM_FMTBIT_U24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | \ SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_DSD_U8 | \ + SNDRV_PCM_FMTBIT_DSD_U16_LE | \ + SNDRV_PCM_FMTBIT_DSD_U32_LE | \ SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) /* * The dummy CODEC is only meant to be used in situations where there is no diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 2a6b84d2781e..fa344968986a 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -10,7 +10,6 @@ #include <linux/firmware.h> #include <linux/module.h> -#include <asm/unaligned.h> #include <sound/soc.h> #include <sound/sof.h> #include "sof-priv.h" @@ -196,67 +195,9 @@ out: EXPORT_SYMBOL(snd_sof_get_status); /* - * Generic buffer page table creation. - * Take the each physical page address and drop the least significant unused - * bits from each (based on PAGE_SIZE). Then pack valid page address bits - * into compressed page table. - */ - -int snd_sof_create_page_table(struct snd_sof_dev *sdev, - struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size) -{ - int i, pages; - - pages = snd_sgbuf_aligned_pages(size); - - dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n", - dmab->area, size, pages); - - for (i = 0; i < pages; i++) { - /* - * The number of valid address bits for each page is 20. - * idx determines the byte position within page_table - * where the current page's address is stored - * in the compressed page_table. - * This can be calculated by multiplying the page number by 2.5. - */ - u32 idx = (5 * i) >> 1; - u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; - u8 *pg_table; - - dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); - - pg_table = (u8 *)(page_table + idx); - - /* - * pagetable compression: - * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 - * ___________pfn 0__________ __________pfn 1___________ _pfn 2... - * .... .... .... .... .... .... .... .... .... .... .... - * It is created by: - * 1. set current location to 0, PFN index i to 0 - * 2. put pfn[i] at current location in Little Endian byte order - * 3. calculate an intermediate value as - * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) - * 4. put x at offset (current location + 2) in LE byte order - * 5. increment current location by 5 bytes, increment i by 2 - * 6. continue to (2) - */ - if (i & 1) - put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, - pg_table); - else - put_unaligned_le32(pfn, pg_table); - } - - return pages; -} - -/* * SOF Driver enumeration. */ -static int sof_machine_check(struct snd_sof_dev *sdev) +int sof_machine_check(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; #if IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) @@ -277,8 +218,10 @@ static int sof_machine_check(struct snd_sof_dev *sdev) if (!machine) return -ENOMEM; - ret = sof_nocodec_setup(sdev->dev, plat_data, machine, - plat_data->desc, plat_data->desc->ops); + machine->drv_name = "sof-nocodec"; + plat_data->fw_filename = plat_data->desc->nocodec_fw_filename; + plat_data->tplg_filename = plat_data->desc->nocodec_tplg_filename; + ret = sof_nocodec_setup(sdev->dev, plat_data->desc->ops); if (ret < 0) return ret; @@ -287,53 +230,45 @@ static int sof_machine_check(struct snd_sof_dev *sdev) return 0; #endif } +EXPORT_SYMBOL(sof_machine_check); -/* - * FW Boot State Transition Diagram - * - * +-----------------------------------------------------------------------+ - * | | - * ------------------ ------------------ | - * | | | | | - * | BOOT_FAILED | | READY_FAILED |-------------------------+ | - * | | | | | | - * ------------------ ------------------ | | - * ^ ^ | | - * | | | | - * (FW Boot Timeout) (FW_READY FAIL) | | - * | | | | - * | | | | - * ------------------ | ------------------ | | - * | | | | | | | - * | IN_PROGRESS |---------------+------------->| COMPLETE | | | - * | | (FW Boot OK) (FW_READY OK) | | | | - * ------------------ ------------------ | | - * ^ | | | - * | | | | - * (FW Loading OK) (System Suspend/Runtime Suspend) - * | | | | - * | | | | - * ------------------ ------------------ | | | - * | | | |<-----+ | | - * | PREPARE | | NOT_STARTED |<---------------------+ | - * | | | |<---------------------------+ - * ------------------ ------------------ - * | ^ | ^ - * | | | | - * | +-----------------------+ | - * | (DSP Probe OK) | - * | | - * | | - * +------------------------------------+ - * (System Suspend/Runtime Suspend) - */ - -static int sof_probe_continue(struct snd_sof_dev *sdev) +int sof_machine_register(struct snd_sof_dev *sdev, void *pdata) { - struct snd_sof_pdata *plat_data = sdev->pdata; + struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata; const char *drv_name; const void *mach; int size; + + drv_name = plat_data->machine->drv_name; + mach = (const void *)plat_data->machine; + size = sizeof(*plat_data->machine); + + /* register machine driver, pass machine info as pdata */ + plat_data->pdev_mach = + platform_device_register_data(sdev->dev, drv_name, + PLATFORM_DEVID_NONE, mach, size); + if (IS_ERR(plat_data->pdev_mach)) + return PTR_ERR(plat_data->pdev_mach); + + dev_dbg(sdev->dev, "created machine %s\n", + dev_name(&plat_data->pdev_mach->dev)); + + return 0; +} +EXPORT_SYMBOL(sof_machine_register); + +void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) +{ + struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata; + + if (!IS_ERR_OR_NULL(plat_data->pdev_mach)) + platform_device_unregister(plat_data->pdev_mach); +} +EXPORT_SYMBOL(sof_machine_unregister); + +static int sof_probe_continue(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; int ret; /* probe the DSP hardware */ @@ -346,7 +281,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) sdev->fw_state = SOF_FW_BOOT_PREPARE; /* check machine info */ - ret = sof_machine_check(sdev); + ret = snd_sof_machine_check(sdev); if (ret < 0) { dev_err(sdev->dev, "error: failed to get machine info %d\n", ret); @@ -419,22 +354,17 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_trace_err; } - drv_name = plat_data->machine->drv_name; - mach = (const void *)plat_data->machine; - size = sizeof(*plat_data->machine); - - /* register machine driver, pass machine info as pdata */ - plat_data->pdev_mach = - platform_device_register_data(sdev->dev, drv_name, - PLATFORM_DEVID_NONE, mach, size); - - if (IS_ERR(plat_data->pdev_mach)) { - ret = PTR_ERR(plat_data->pdev_mach); - goto fw_trace_err; - } + ret = snd_sof_machine_register(sdev, plat_data); + if (ret < 0) + goto fw_run_err; - dev_dbg(sdev->dev, "created machine %s\n", - dev_name(&plat_data->pdev_mach->dev)); + /* + * Some platforms in SOF, ex: BYT, may not have their platform PM + * callbacks set. Increment the usage count so as to + * prevent the device from entering runtime suspend. + */ + if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume) + pm_runtime_get_noresume(sdev->dev); if (plat_data->sof_probe_complete) plat_data->sof_probe_complete(sdev->dev); @@ -545,9 +475,7 @@ int snd_sof_device_remove(struct device *dev) * will remove the component driver and unload the topology * before freeing the snd_card. */ - if (!IS_ERR_OR_NULL(pdata->pdev_mach)) - platform_device_unregister(pdata->pdev_mach); - + snd_sof_machine_unregister(sdev, pdata); /* * Unregistering the machine driver results in unloading the topology. * Some widgets, ex: scheduler, attempt to power down the core they are diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig index b4f0426685c4..805e8b43088e 100644 --- a/sound/soc/sof/imx/Kconfig +++ b/sound/soc/sof/imx/Kconfig @@ -12,7 +12,7 @@ config SND_SOC_SOF_IMX_TOPLEVEL if SND_SOC_SOF_IMX_TOPLEVEL config SND_SOC_SOF_IMX8_SUPPORT - bool "SOF support for i.MX8" + tristate "SOF support for i.MX8" depends on IMX_SCU select IMX_DSP help @@ -21,7 +21,22 @@ config SND_SOC_SOF_IMX8_SUPPORT If unsure select "N". config SND_SOC_SOF_IMX8 + tristate + +config SND_SOC_SOF_IMX8M_SUPPORT + tristate "SOF support for i.MX8M" + depends on IMX_DSP + help + This adds support for Sound Open Firmware for NXP i.MX8M platforms + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_IMX8M + tristate + +config SND_SOC_SOF_IMX def_tristate SND_SOC_SOF_OF - depends on SND_SOC_SOF_IMX8_SUPPORT + select SND_SOC_SOF_IMX8 if SND_SOC_SOF_IMX8_SUPPORT + select SND_SOC_SOF_IMX8M if SND_SOC_SOF_IMX8M_SUPPORT endif ## SND_SOC_SOF_IMX_IMX_TOPLEVEL diff --git a/sound/soc/sof/imx/Makefile b/sound/soc/sof/imx/Makefile index 6ef908e8c807..d9d8dc1765b8 100644 --- a/sound/soc/sof/imx/Makefile +++ b/sound/soc/sof/imx/Makefile @@ -1,4 +1,6 @@ # SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) snd-sof-imx8-objs := imx8.o +snd-sof-imx8m-objs := imx8m.o obj-$(CONFIG_SND_SOC_SOF_IMX8) += snd-sof-imx8.o +obj-$(CONFIG_SND_SOC_SOF_IMX8M) += snd-sof-imx8m.o diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index 69785f688ddf..8c415884a2df 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -6,6 +6,7 @@ // // Hardware interface for audio DSP on i.MX8 +#include <linux/clk.h> #include <linux/firmware.h> #include <linux/of_platform.h> #include <linux/of_address.h> @@ -39,9 +40,19 @@ #define MBOX_OFFSET 0x800000 #define MBOX_SIZE 0x1000 +#define IMX8_DSP_CLK_NUM 9 +static const char *imx8_dsp_clks_names[IMX8_DSP_CLK_NUM] = +{ + /* ESAI0 clocks */ + "esai0_core", "esai0_extal", "esai0_fsys", "esai0_spba", + /* SAI1 clocks */ + "sai1_bus", "sai1_mclk0", "sai1_mclk1", "sai1_mclk2", "sai1_mclk3", +}; + struct imx8_priv { struct device *dev; struct snd_sof_dev *sdev; + bool suspended; /* DSP IPC handler */ struct imx_dsp_ipc *dsp_ipc; @@ -55,8 +66,54 @@ struct imx8_priv { struct device **pd_dev; struct device_link **link; + struct clk *dsp_clks[IMX8_DSP_CLK_NUM]; }; +static int imx8_init_clocks(struct snd_sof_dev *sdev) +{ + int i; + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + + for (i = 0; i < IMX8_DSP_CLK_NUM; i++) { + priv->dsp_clks[i] = devm_clk_get(priv->dev, imx8_dsp_clks_names[i]); + if (IS_ERR(priv->dsp_clks[i])) + priv->dsp_clks[i] = NULL; + } + + return 0; +} + +static int imx8_prepare_clocks(struct snd_sof_dev *sdev) +{ + int i, ret; + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + + for (i = 0; i < IMX8_DSP_CLK_NUM; i++) { + ret = clk_prepare_enable(priv->dsp_clks[i]); + if (ret < 0) { + dev_err(priv->dev, "Failed to enable clk %s\n", + imx8_dsp_clks_names[i]); + goto err_dsp_clks; + } + } + return 0; + +err_dsp_clks: + while (--i >= 0) + clk_disable_unprepare(priv->dsp_clks[i]); + + return ret; +} + +static void imx8_disable_clocks(struct snd_sof_dev *sdev) +{ + int i; + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + + for (i = 0; i < IMX8_DSP_CLK_NUM; i++) + clk_disable_unprepare(priv->dsp_clks[i]); +} + static void imx8_get_reply(struct snd_sof_dev *sdev) { struct snd_sof_ipc_msg *msg = sdev->msg; @@ -119,7 +176,7 @@ static void imx8_dsp_handle_request(struct imx_dsp_ipc *ipc) snd_sof_ipc_msgs_rx(priv->sdev); } -struct imx_dsp_ops dsp_ops = { +static struct imx_dsp_ops dsp_ops = { .handle_reply = imx8_dsp_handle_reply, .handle_request = imx8_dsp_handle_request, }; @@ -138,7 +195,7 @@ static int imx8_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) /* * DSP control. */ -static int imx8_run(struct snd_sof_dev *sdev) +static int imx8x_run(struct snd_sof_dev *sdev) { struct imx8_priv *dsp_priv = (struct imx8_priv *)sdev->private; int ret; @@ -178,6 +235,24 @@ static int imx8_run(struct snd_sof_dev *sdev) return 0; } +static int imx8_run(struct snd_sof_dev *sdev) +{ + struct imx8_priv *dsp_priv = (struct imx8_priv *)sdev->private; + int ret; + + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 0); + if (ret < 0) { + dev_err(sdev->dev, "Error system address offset source select\n"); + return ret; + } + + imx_sc_pm_cpu_start(dsp_priv->sc_ipc, IMX_SC_R_DSP, true, + RESET_VECTOR_VADDR); + + return 0; +} + static int imx8_probe(struct snd_sof_dev *sdev) { struct platform_device *pdev = @@ -189,7 +264,7 @@ static int imx8_probe(struct snd_sof_dev *sdev) struct resource res; u32 base, size; int ret = 0; - int i; + int i = 0; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -207,6 +282,10 @@ static int imx8_probe(struct snd_sof_dev *sdev) return priv->num_domains; } + /* power domain already enabled by PM core */ + if (priv->num_domains == 1) + goto done_pm; + priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains, sizeof(*priv->pd_dev), GFP_KERNEL); if (!priv->pd_dev) @@ -234,6 +313,7 @@ static int imx8_probe(struct snd_sof_dev *sdev) } } +done_pm: ret = imx_scu_get_handle(&priv->sc_ipc); if (ret) { dev_err(sdev->dev, "Cannot obtain SCU handle (err = %d)\n", @@ -307,6 +387,9 @@ static int imx8_probe(struct snd_sof_dev *sdev) /* set default mailbox offset for FW ready message */ sdev->dsp_box.offset = MBOX_OFFSET; + imx8_init_clocks(sdev); + imx8_prepare_clocks(sdev); + return 0; exit_pdev_unregister: @@ -355,13 +438,78 @@ static int imx8_ipc_pcm_params(struct snd_sof_dev *sdev, return 0; } +int imx8_resume(struct snd_sof_dev *sdev) +{ + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + int i; + + imx8_prepare_clocks(sdev); + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_request_channel(priv->dsp_ipc, i); + + return 0; +} + +int imx8_suspend(struct snd_sof_dev *sdev) +{ + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + int i; + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_free_channel(priv->dsp_ipc, i); + + imx8_disable_clocks(sdev); + + return 0; +} + +int imx8_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + return imx8_resume(sdev); +} + +int imx8_dsp_runtime_suspend(struct snd_sof_dev *sdev) +{ + + return imx8_suspend(sdev); +} + +int imx8_dsp_resume(struct snd_sof_dev *sdev) +{ + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + + if (priv->suspended) { + imx8_resume(sdev); + priv->suspended = false; + } + + return 0; +} + +int imx8_dsp_suspend(struct snd_sof_dev *sdev) +{ + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; + + if (!priv->suspended) { + imx8_suspend(sdev); + priv->suspended = true; + } + + return 0; +} + + static struct snd_soc_dai_driver imx8_dai[] = { { - .name = "esai-port", + .name = "esai0", +}, +{ + .name = "sai1", }, }; -/* i.MX8 ops */ +/* i.MX8 ops */ struct snd_sof_dsp_ops sof_imx8_ops = { /* probe and remove */ .probe = imx8_probe, @@ -390,8 +538,67 @@ struct snd_sof_dsp_ops sof_imx8_ops = { /* DAI drivers */ .drv = imx8_dai, - .num_drv = 1, /* we have only 1 ESAI interface on i.MX8 */ + .num_drv = 2, /* we use ESAI0 / SAI1 i.MX8 */ + + /* PM */ + .suspend = imx8_dsp_suspend, + .resume = imx8_dsp_resume, + .runtime_suspend = imx8_dsp_runtime_suspend, + .runtime_resume = imx8_dsp_runtime_resume, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP }; EXPORT_SYMBOL(sof_imx8_ops); +/* i.MX8X ops */ +struct snd_sof_dsp_ops sof_imx8x_ops = { + /* probe and remove */ + .probe = imx8_probe, + .remove = imx8_remove, + /* DSP core boot */ + .run = imx8x_run, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* ipc */ + .send_msg = imx8_send_msg, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = imx8_get_mailbox_offset, + .get_window_offset = imx8_get_window_offset, + + .ipc_msg_data = imx8_ipc_msg_data, + .ipc_pcm_params = imx8_ipc_pcm_params, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + .get_bar_index = imx8_get_bar_index, + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* DAI drivers */ + .drv = imx8_dai, + .num_drv = 2, /* we use ESAI0 / SAI1 on i.MX8X*/ + + /* PM */ + .suspend = imx8_dsp_suspend, + .resume = imx8_dsp_resume, + .runtime_suspend = imx8_dsp_runtime_suspend, + .runtime_resume = imx8_dsp_runtime_resume, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP +}; +EXPORT_SYMBOL(sof_imx8x_ops); + MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/imx/imx8m.c b/sound/soc/sof/imx/imx8m.c new file mode 100644 index 000000000000..09e136154efc --- /dev/null +++ b/sound/soc/sof/imx/imx8m.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// +// Copyright 2019 NXP +// +// Author: Daniel Baluta <daniel.baluta@nxp.com> +// +// Hardware interface for audio DSP on i.MX8 + +#include <linux/firmware.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/pm_domain.h> + +#include <linux/module.h> +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include <linux/firmware/imx/dsp.h> +#include <linux/clk.h> +#include <linux/bits.h> + +#include "../ops.h" +#include "../../fsl/fsl_dsp_audiomix.h" + +/* DSP memories */ +#define IRAM_OFFSET 0x10000 +#define IRAM_SIZE (2 * 1024) +#define DRAM0_OFFSET 0x0 +#define DRAM0_SIZE (32 * 1024) +#define DRAM1_OFFSET 0x8000 +#define DRAM1_SIZE (32 * 1024) +#define SYSRAM_OFFSET 0x18000 +#define SYSRAM_SIZE (256 * 1024) +#define SYSROM_OFFSET 0x58000 +#define SYSROM_SIZE (192 * 1024) + +#define RESET_VECTOR_VADDR 0x596f8000 + +#define MBOX_OFFSET 0x800000 +#define MBOX_SIZE 0x1000 + +#define IMX8M_DAP_DEBUG 0x28800000 +#define IMX8M_DAP_DEBUG_SIZE (64 * 1024) +#define IMX8M_DAP_PWRCTL (0x4000 + 0x3020) +#define IMX8M_PWRCTL_CORERESET BIT(16) + +#define IMX8M_DSP_CLK_NUM 9 +static const char *imx8m_dsp_clks[IMX8M_DSP_CLK_NUM] = { + "ocram", + "core", + "debug", + "sdma3", + "sai3_bus", + "sai3_mclk0", + "sai3_mclk1", + "sai3_mclk2", + "sai3_mclk3", +}; + +struct imx8m_priv { + struct device *dev; + struct snd_sof_dev *sdev; + bool suspended; + + struct imx_audiomix_dsp_data *audiomix; + + /* DSP IPC handler */ + struct imx_dsp_ipc *dsp_ipc; + struct platform_device *ipc_dev; + + /* Power domain handling */ + int num_domains; + struct device **pd_dev; + struct device_link **link; + + struct clk *clks[IMX8M_DSP_CLK_NUM]; + void __iomem *dap; +}; + +int imx8m_dsp_configure_audmix(struct imx8m_priv *dsp_priv) +{ + struct device_node *np; + struct platform_device *pdev; + + np = of_find_node_by_name(NULL, "audiomix_dsp"); + if (!np) + return -EPROBE_DEFER; + + pdev = of_find_device_by_node(np); + if (!pdev) + return -EPROBE_DEFER; + + dsp_priv->audiomix = dev_get_drvdata(&pdev->dev); + if (!dsp_priv->audiomix) + return -EPROBE_DEFER; + + return 0; +} + +static void imx8m_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + int ret = 0; + + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt\n"); + return; + } + + /* get reply */ + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); + + if (reply.error < 0) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply has correct size? */ + if (reply.hdr.size != msg->reply_size) { + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + + /* read the message */ + if (msg->reply_size > 0) + sof_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + msg->reply_error = ret; +} + +static int imx8m_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int imx8m_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static void imx8m_dsp_handle_reply(struct imx_dsp_ipc *ipc) +{ + struct imx8m_priv *priv = imx_dsp_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + imx8m_get_reply(priv->sdev); + snd_sof_ipc_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void imx8m_dsp_handle_request(struct imx_dsp_ipc *ipc) +{ + struct imx8m_priv *priv = imx_dsp_get_data(ipc); + + snd_sof_ipc_msgs_rx(priv->sdev); +} + +static struct imx_dsp_ops dsp_ops = { + .handle_reply = imx8m_dsp_handle_reply, + .handle_request = imx8m_dsp_handle_request, +}; + +static int imx8m_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + imx_dsp_ring_doorbell(priv->dsp_ipc, 0); + + return 0; +} + +/* + * DSP control. + */ +static int imx8m_run(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *dsp_priv = (struct imx8m_priv *)sdev->private; + + imx_audiomix_dsp_start(dsp_priv->audiomix); + + return 0; +} + +static int imx8m_reset(struct snd_sof_dev *sdev) { + + struct imx8m_priv *dsp_priv = (struct imx8m_priv *)sdev->private; + u32 pwrctl; + + /* put DSP into reset and stall */ + pwrctl = readl(dsp_priv->dap + IMX8M_DAP_PWRCTL); + pwrctl |= IMX8M_PWRCTL_CORERESET; + writel(pwrctl, dsp_priv->dap + IMX8M_DAP_PWRCTL); + + /* keep reset asserted for 10 cycles */ + usleep_range(1, 2); + + imx_audiomix_dsp_stall(dsp_priv->audiomix); + + /* take the DSP out of reset and keep stalled for FW loading */ + pwrctl = readl(dsp_priv->dap + IMX8M_DAP_PWRCTL); + pwrctl &= ~IMX8M_PWRCTL_CORERESET; + writel(pwrctl, dsp_priv->dap + IMX8M_DAP_PWRCTL); + + return 0; +} + +static int imx8m_probe(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct device_node *np = pdev->dev.of_node; + struct device_node *res_node; + struct resource *mmio; + struct imx8m_priv *priv; + struct resource res; + u32 base, size; + int ret = 0; + int i = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->private = priv; + priv->dev = sdev->dev; + priv->sdev = sdev; + + ret = imx8m_dsp_configure_audmix(priv); + if (ret < 0) + return ret; + + /* power up device associated power domains */ + priv->num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + if (priv->num_domains < 0) { + dev_err(sdev->dev, "no power-domains property in %pOF\n", np); + return priv->num_domains; + } + + /* power domain already enabled by PM core */ + if (priv->num_domains == 1) + goto done_pm; + + priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains, + sizeof(*priv->pd_dev), GFP_KERNEL); + if (!priv->pd_dev) + return -ENOMEM; + + priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains, + sizeof(*priv->link), GFP_KERNEL); + if (!priv->link) + return -ENOMEM; + + for (i = 0; i < priv->num_domains; i++) { + priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i); + if (IS_ERR(priv->pd_dev[i])) { + ret = PTR_ERR(priv->pd_dev[i]); + goto exit_unroll_pm; + } + priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!priv->link[i]) { + ret = -ENOMEM; + dev_pm_domain_detach(priv->pd_dev[i], false); + goto exit_unroll_pm; + } + } + +done_pm: + priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) { + ret = PTR_ERR(priv->ipc_dev); + goto exit_unroll_pm; + } + + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dsp_ipc) { + /* DSP IPC driver not probed yet, try later */ + ret = -EPROBE_DEFER; + dev_err(sdev->dev, "Failed to get drvdata\n"); + goto exit_pdev_unregister; + } + + imx_dsp_set_data(priv->dsp_ipc, priv); + priv->dsp_ipc->ops = &dsp_ops; + + /* DSP base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n"); + ret = -EINVAL; + goto exit_pdev_unregister; + } + + priv->dap = devm_ioremap(sdev->dev, IMX8M_DAP_DEBUG, IMX8M_DAP_DEBUG_SIZE); + if (!priv->dap ) { + dev_err(sdev->dev, "error: failed to map DAP debug memory area"); + return -ENODEV; + } + + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { + dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n", + base, size); + ret = -ENODEV; + goto exit_pdev_unregister; + } + sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM; + + res_node = of_parse_phandle(np, "memory-region", 0); + if (!res_node) { + dev_err(&pdev->dev, "failed to get memory region node\n"); + ret = -ENODEV; + goto exit_pdev_unregister; + } + + ret = of_address_to_resource(res_node, 0, &res); + if (ret) { + dev_err(&pdev->dev, "failed to get reserved region address\n"); + goto exit_pdev_unregister; + } + + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start, + res.end - res.start + + 1); + if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) { + dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n", + base, size); + ret = -ENOMEM; + goto exit_pdev_unregister; + } + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = MBOX_OFFSET; + + for (i = 0; i < IMX8M_DSP_CLK_NUM; i++) { + priv->clks[i] = devm_clk_get(&pdev->dev, imx8m_dsp_clks[i]); + if (IS_ERR(priv->clks[i])) + priv->clks[i] = NULL; + } + /* TODO: handle clocks at PM suspend/resume time */ + for (i = 0; i < IMX8M_DSP_CLK_NUM; i++) + clk_prepare_enable(priv->clks[i]); + + return 0; + +exit_pdev_unregister: + platform_device_unregister(priv->ipc_dev); +exit_unroll_pm: + while (--i >= 0) { + device_link_del(priv->link[i]); + dev_pm_domain_detach(priv->pd_dev[i], false); + } + return ret; +} + +static int imx8m_remove(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private; + int i; + + platform_device_unregister(priv->ipc_dev); + + for (i = 0; i < priv->num_domains; i++) { + device_link_del(priv->link[i]); + dev_pm_domain_detach(priv->pd_dev[i], false); + } + + for (i = 0; i < IMX8M_DSP_CLK_NUM; i++) + clk_disable_unprepare(priv->clks[i]); + + return 0; +} + +/* on i.MX8 there is 1 to 1 match between type and BAR idx */ +static int imx8m_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + return type; +} + +static void imx8m_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); +} + +static int imx8m_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + return 0; +} + +static struct snd_soc_dai_driver imx8m_dai[] = { +{ + .name = "sai3", +}, +}; + +int imx8m_resume(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private; + int i; + + for (i = 0; i < IMX8M_DSP_CLK_NUM; i++) + clk_prepare_enable(priv->clks[i]); + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_request_channel(priv->dsp_ipc, i); + + return 0; +} + +int imx8m_suspend(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private; + int i; + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_free_channel(priv->dsp_ipc, i); + + for (i = 0; i < IMX8M_DSP_CLK_NUM; i++) + clk_disable_unprepare(priv->clks[i]); + + return 0; +} + +static int imx8m_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + return imx8m_resume(sdev); +} + +static int imx8m_dsp_runtime_suspend(struct snd_sof_dev *sdev) +{ + return imx8m_suspend(sdev); +} + +static int imx8m_dsp_resume(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private; + + if (priv->suspended) { + imx8m_resume(sdev); + priv->suspended = false; + } + + return 0; +} + +static int imx8m_dsp_suspend(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private; + + if (!priv->suspended) { + imx8m_suspend(sdev); + priv->suspended = true; + } + + return 0; +} + +/* i.MX8 ops */ +struct snd_sof_dsp_ops sof_imx8m_ops = { + /* probe and remove */ + .probe = imx8m_probe, + .remove = imx8m_remove, + /* DSP core boot */ + .run = imx8m_run, + .reset = imx8m_reset, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* ipc */ + .send_msg = imx8m_send_msg, + .fw_ready = sof_fw_ready, + .get_mailbox_offset = imx8m_get_mailbox_offset, + .get_window_offset = imx8m_get_window_offset, + + .ipc_msg_data = imx8m_ipc_msg_data, + .ipc_pcm_params = imx8m_ipc_pcm_params, + + /* module loading */ + .load_module = snd_sof_parse_module_memcpy, + .get_bar_index = imx8m_get_bar_index, + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* DAI drivers */ + .drv = imx8m_dai, + .num_drv = 1, /* we have only 1 ESAI interface on i.MX8 */ + + .suspend = imx8m_dsp_suspend, + .resume = imx8m_dsp_resume, + + .runtime_suspend = imx8m_dsp_runtime_suspend, + .runtime_resume = imx8m_dsp_runtime_resume, + + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, +}; +EXPORT_SYMBOL(sof_imx8m_ops); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 8dc7a5558da4..336e48e77b01 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -53,6 +53,11 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .ipc_msg_data = hda_ipc_msg_data, .ipc_pcm_params = hda_ipc_pcm_params, + /* machine driver */ + .machine_check = sof_machine_check, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + /* debug */ .debug_map = apl_dsp_debugfs, .debug_map_count = ARRAY_SIZE(apl_dsp_debugfs), @@ -97,6 +102,13 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .runtime_resume = hda_dsp_runtime_resume, .runtime_idle = hda_dsp_runtime_idle, .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, }; EXPORT_SYMBOL(sof_apl_ops); diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c index 80e2826fb447..a0944da7cf6a 100644 --- a/sound/soc/sof/intel/bdw.c +++ b/sound/soc/sof/intel/bdw.c @@ -483,8 +483,11 @@ static int bdw_probe(struct snd_sof_dev *sdev) /* register our IRQ */ sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc); - if (sdev->ipc_irq < 0) + if (sdev->ipc_irq < 0) { + dev_err(sdev->dev, "error: failed to get IRQ at index %d\n", + desc->irqindex_host_ipc); return sdev->ipc_irq; + } dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq); ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq, @@ -554,6 +557,11 @@ const struct snd_sof_dsp_ops sof_bdw_ops = { .ipc_msg_data = intel_ipc_msg_data, .ipc_pcm_params = intel_ipc_pcm_params, + /* machine driver */ + .machine_check = sof_machine_check, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + /* debug */ .debug_map = bdw_debugfs, .debug_map_count = ARRAY_SIZE(bdw_debugfs), @@ -571,7 +579,14 @@ const struct snd_sof_dsp_ops sof_bdw_ops = { /* DAI drivers */ .drv = bdw_dai, - .num_drv = ARRAY_SIZE(bdw_dai) + .num_drv = ARRAY_SIZE(bdw_dai), + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, }; EXPORT_SYMBOL(sof_bdw_ops); diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c index 41008c974ac6..702576addd9a 100644 --- a/sound/soc/sof/intel/byt.c +++ b/sound/soc/sof/intel/byt.c @@ -494,6 +494,11 @@ const struct snd_sof_dsp_ops sof_tng_ops = { .ipc_msg_data = intel_ipc_msg_data, .ipc_pcm_params = intel_ipc_pcm_params, + /* machine driver */ + .machine_check = sof_machine_check, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + /* debug */ .debug_map = byt_debugfs, .debug_map_count = ARRAY_SIZE(byt_debugfs), @@ -512,6 +517,13 @@ const struct snd_sof_dsp_ops sof_tng_ops = { /* DAI drivers */ .drv = byt_dai, .num_drv = 3, /* we have only 3 SSPs on byt*/ + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, }; EXPORT_SYMBOL(sof_tng_ops); @@ -600,8 +612,11 @@ static int byt_acpi_probe(struct snd_sof_dev *sdev) irq: /* register our IRQ */ sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc); - if (sdev->ipc_irq < 0) + if (sdev->ipc_irq < 0) { + dev_err(sdev->dev, "error: failed to get IRQ at index %d\n", + desc->irqindex_host_ipc); return sdev->ipc_irq; + } dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq); ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq, @@ -655,6 +670,11 @@ const struct snd_sof_dsp_ops sof_byt_ops = { .ipc_msg_data = intel_ipc_msg_data, .ipc_pcm_params = intel_ipc_pcm_params, + /* machine driver */ + .machine_check = sof_machine_check, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + /* debug */ .debug_map = byt_debugfs, .debug_map_count = ARRAY_SIZE(byt_debugfs), @@ -673,6 +693,13 @@ const struct snd_sof_dsp_ops sof_byt_ops = { /* DAI drivers */ .drv = byt_dai, .num_drv = 3, /* we have only 3 SSPs on byt*/ + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, }; EXPORT_SYMBOL(sof_byt_ops); @@ -714,6 +741,11 @@ const struct snd_sof_dsp_ops sof_cht_ops = { .ipc_msg_data = intel_ipc_msg_data, .ipc_pcm_params = intel_ipc_pcm_params, + /* machine driver */ + .machine_check = sof_machine_check, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + /* debug */ .debug_map = cht_debugfs, .debug_map_count = ARRAY_SIZE(cht_debugfs), @@ -733,6 +765,13 @@ const struct snd_sof_dsp_ops sof_cht_ops = { .drv = byt_dai, /* all 6 SSPs may be available for cherrytrail */ .num_drv = ARRAY_SIZE(byt_dai), + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, }; EXPORT_SYMBOL(sof_cht_ops); diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 4ddd73762d81..9a19903befa2 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -211,6 +211,11 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .ipc_msg_data = hda_ipc_msg_data, .ipc_pcm_params = hda_ipc_pcm_params, + /* machine driver */ + .machine_check = sof_machine_check, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + /* debug */ .debug_map = cnl_dsp_debugfs, .debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs), @@ -255,6 +260,13 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .runtime_resume = hda_dsp_runtime_resume, .runtime_idle = hda_dsp_runtime_idle, .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, }; EXPORT_SYMBOL(sof_cnl_ops); diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index ce114df5e4fc..235be4fc0862 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -32,6 +32,42 @@ static int get_ext_windows(struct snd_sof_dev *sdev, return 0; } +static int get_cc_info(struct snd_sof_dev *sdev, + struct sof_ipc_ext_data_hdr *ext_hdr) +{ + int ret; + + struct sof_ipc_cc_version *cc = + container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr); + + dev_dbg(sdev->dev, "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n", + cc->name, cc->major, cc->minor, cc->micro, cc->desc, + cc->optim); + + /* create read-only cc_version debugfs to store compiler version info */ + /* use local copy of the cc_version to prevent data corruption */ + if (sdev->first_boot) { + sdev->cc_version = devm_kmalloc(sdev->dev, cc->ext_hdr.hdr.size, + GFP_KERNEL); + + if (!sdev->cc_version) + return -ENOMEM; + + memcpy(sdev->cc_version, cc, cc->ext_hdr.hdr.size); + ret = snd_sof_debugfs_buf_item(sdev, sdev->cc_version, + cc->ext_hdr.hdr.size, + "cc_version", 0444); + + /* errors are only due to memory allocation, not debugfs */ + if (ret < 0) { + dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n"); + return ret; + } + } + + return 0; +} + /* parse the extended FW boot data structures from FW boot message */ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) { @@ -50,8 +86,7 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { /* read in ext structure */ - offset += sizeof(*ext_hdr); - snd_sof_dsp_block_read(sdev, bar, offset, + snd_sof_dsp_block_read(sdev, bar, offset + sizeof(*ext_hdr), (void *)((u8 *)ext_data + sizeof(*ext_hdr)), ext_hdr->hdr.size - sizeof(*ext_hdr)); @@ -61,13 +96,18 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) /* process structure data */ switch (ext_hdr->type) { case SOF_IPC_EXT_DMA_BUFFER: + ret = 0; break; case SOF_IPC_EXT_WINDOW: ret = get_ext_windows(sdev, ext_hdr); break; + case SOF_IPC_EXT_CC_INFO: + ret = get_cc_info(sdev, ext_hdr); + break; default: dev_warn(sdev->dev, "warning: unknown ext header type %d size 0x%x\n", ext_hdr->type, ext_hdr->hdr.size); + ret = 0; break; } diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c index 71410116add1..849c3bcdca9e 100644 --- a/sound/soc/sof/nocodec.c +++ b/sound/soc/sof/nocodec.c @@ -66,23 +66,11 @@ static int sof_nocodec_bes_setup(struct device *dev, } int sof_nocodec_setup(struct device *dev, - struct snd_sof_pdata *sof_pdata, - struct snd_soc_acpi_mach *mach, - const struct sof_dev_desc *desc, const struct snd_sof_dsp_ops *ops) { struct snd_soc_dai_link *links; int ret; - if (!mach) - return -EINVAL; - - sof_pdata->drv_name = "sof-nocodec"; - - mach->drv_name = "sof-nocodec"; - sof_pdata->fw_filename = desc->nocodec_fw_filename; - sof_pdata->tplg_filename = desc->nocodec_tplg_filename; - /* create dummy BE dai_links */ links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * ops->num_drv, GFP_KERNEL); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 824d36fe59fd..97f4feb73368 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -381,6 +381,32 @@ snd_sof_pcm_platform_pointer(struct snd_sof_dev *sdev, return 0; } +/* machine driver */ +static inline int +snd_sof_machine_register(struct snd_sof_dev *sdev, void *pdata) +{ + if (sof_ops(sdev) && sof_ops(sdev)->machine_register) + return sof_ops(sdev)->machine_register(sdev, pdata); + + return 0; +} + +static inline void +snd_sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) +{ + if (sof_ops(sdev) && sof_ops(sdev)->machine_unregister) + sof_ops(sdev)->machine_unregister(sdev, pdata); +} + +static inline int +snd_sof_machine_check(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev) && sof_ops(sdev)->machine_check) + return sof_ops(sdev)->machine_check(sdev); + + return 0; +} + static inline const struct snd_sof_dsp_ops *sof_get_ops(const struct sof_dev_desc *d, const struct sof_ops_table mach_ops[], int asize) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 2b876d497447..ed01523affba 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -34,7 +34,7 @@ static int create_page_table(struct snd_pcm_substream *substream, if (!spcm) return -EINVAL; - return snd_sof_create_page_table(sdev, dmab, + return snd_sof_create_page_table(sdev->dev, dmab, spcm->stream[stream].page_table.area, size); } @@ -435,6 +435,7 @@ static int sof_pcm_open(struct snd_pcm_substream *substream) struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + const struct snd_sof_dsp_ops *ops = sof_ops(sdev); struct snd_sof_pcm *spcm; struct snd_soc_tplg_stream_caps *caps; int ret; @@ -464,11 +465,8 @@ static int sof_pcm_open(struct snd_pcm_substream *substream) le32_to_cpu(caps->period_size_min)); /* set runtime config */ - runtime->hw.info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP; + runtime->hw.info = ops->hw_info; /* platform-specific */ + runtime->hw.formats = le64_to_cpu(caps->formats); runtime->hw.period_bytes_min = le32_to_cpu(caps->period_size_min); runtime->hw.period_bytes_max = le32_to_cpu(caps->period_size_max); @@ -691,6 +689,19 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, case SOF_DAI_INTEL_ALH: /* do nothing for ALH dai_link */ break; + case SOF_DAI_IMX_ESAI: + channels->min = dai->dai_config->esai.tdm_slots; + channels->max = dai->dai_config->esai.tdm_slots; + + break; + case SOF_DAI_IMX_SAI: + channels->min = dai->dai_config->sai.tdm_slots; + channels->max = dai->dai_config->sai.tdm_slots; + + dev_dbg(sdev->dev, + "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; default: dev_err(sdev->dev, "error: invalid DAI type %d\n", dai->dai_config->type); @@ -724,14 +735,6 @@ static int sof_pcm_probe(struct snd_soc_component *component) return ret; } - /* - * Some platforms in SOF, ex: BYT, may not have their platform PM - * callbacks set. Increment the usage count so as to - * prevent the device from entering runtime suspend. - */ - if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume) - pm_runtime_get_noresume(sdev->dev); - return ret; } @@ -747,7 +750,11 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) struct snd_sof_pdata *plat_data = sdev->pdata; const char *drv_name; - drv_name = plat_data->machine->drv_name; + + if (plat_data->machine) + drv_name = plat_data->machine->drv_name; + else + drv_name = plat_data->machine_drv_name; pd->name = "sof-audio-component"; pd->probe = sof_pcm_probe; diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 128680b09c20..789ed6a28ed7 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -290,6 +290,9 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; } + if (!runtime_resume && pm_runtime_suspended(sdev->dev)) + return 0; + sdev->fw_state = SOF_FW_BOOT_PREPARE; /* load the firmware */ diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c index ea7b8b895412..9b77916d8aaf 100644 --- a/sound/soc/sof/sof-acpi-dev.c +++ b/sound/soc/sof/sof-acpi-dev.c @@ -39,6 +39,7 @@ static const struct sof_dev_desc sof_acpi_haswell_desc = { .chip_info = &hsw_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-hsw.ri", .nocodec_fw_filename = "sof-hsw.ri", .nocodec_tplg_filename = "sof-hsw-nocodec.tplg", .ops = &sof_hsw_ops, @@ -56,6 +57,7 @@ static const struct sof_dev_desc sof_acpi_broadwell_desc = { .chip_info = &bdw_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-bdw.ri", .nocodec_fw_filename = "sof-bdw.ri", .nocodec_tplg_filename = "sof-bdw-nocodec.tplg", .ops = &sof_bdw_ops, @@ -75,6 +77,7 @@ static const struct sof_dev_desc sof_acpi_baytrailcr_desc = { .chip_info = &byt_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-byt.ri", .nocodec_fw_filename = "sof-byt.ri", .nocodec_tplg_filename = "sof-byt-nocodec.tplg", .ops = &sof_byt_ops, @@ -90,6 +93,7 @@ static const struct sof_dev_desc sof_acpi_baytrail_desc = { .chip_info = &byt_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-byt.ri", .nocodec_fw_filename = "sof-byt.ri", .nocodec_tplg_filename = "sof-byt-nocodec.tplg", .ops = &sof_byt_ops, @@ -105,6 +109,7 @@ static const struct sof_dev_desc sof_acpi_cherrytrail_desc = { .chip_info = &cht_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-cht.ri", .nocodec_fw_filename = "sof-cht.ri", .nocodec_tplg_filename = "sof-cht-nocodec.tplg", .ops = &sof_cht_ops, @@ -164,7 +169,11 @@ static int sof_acpi_probe(struct platform_device *pdev) mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL); if (!mach) return -ENOMEM; - ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops); + + mach->drv_name = "sof-nocodec"; + sof_pdata->fw_filename = desc->nocodec_fw_filename; + sof_pdata->tplg_filename = desc->nocodec_tplg_filename; + ret = sof_nocodec_setup(dev, ops); if (ret < 0) return ret; #else diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c index 28a9692974e5..befa35ba492f 100644 --- a/sound/soc/sof/sof-of-dev.c +++ b/sound/soc/sof/sof-of-dev.c @@ -13,18 +13,40 @@ #include "ops.h" extern struct snd_sof_dsp_ops sof_imx8_ops; +extern struct snd_sof_dsp_ops sof_imx8x_ops; +extern struct snd_sof_dsp_ops sof_imx8m_ops; /* platform specific devices */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) static struct sof_dev_desc sof_of_imx8qxp_desc = { .default_fw_path = "imx/sof", .default_tplg_path = "imx/sof-tplg", + .default_fw_filename = "sof-imx8x.ri", .nocodec_fw_filename = "sof-imx8.ri", .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", + .ops = &sof_imx8x_ops, +}; + +static struct sof_dev_desc sof_of_imx8qm_desc = { + .default_fw_path = "imx/sof", + .default_tplg_path = "imx/sof-tplg", + .default_fw_filename = "sof-imx8.ri", + .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", .ops = &sof_imx8_ops, }; #endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M) +static struct sof_dev_desc sof_of_imx8mp_desc = { + .default_fw_path = "imx/sof", + .default_tplg_path = "imx/sof-tplg", + .default_fw_filename = "sof-imx8m.ri", + .nocodec_fw_filename = "sof-imx8m.ri", + .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", + .ops = &sof_imx8m_ops, +}; +#endif + static const struct dev_pm_ops sof_of_pm = { SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume) SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume, @@ -36,7 +58,33 @@ static void sof_of_probe_complete(struct device *dev) /* allow runtime_pm */ pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); pm_runtime_enable(dev); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +int sof_of_parse(struct platform_device *pdev) +{ + struct snd_sof_pdata *sof_pdata = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + int ret; + + /* firmware-name is optional in DT */ + of_property_read_string(np, "firmware-name", &sof_pdata->fw_filename); + + ret = of_property_read_string(np, "tplg-name", + &sof_pdata->tplg_filename); + if (ret < 0) + return ret; + + ret = of_property_read_string(np, "machine-drv-name", + &sof_pdata->machine_drv_name); + if (ret < 0) + return ret; + + return 0; } static int sof_of_probe(struct platform_device *pdev) @@ -44,7 +92,9 @@ static int sof_of_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; const struct sof_dev_desc *desc; /*TODO: create a generic snd_soc_xxx_mach */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE) struct snd_soc_acpi_mach *mach; +#endif struct snd_sof_pdata *sof_pdata; const struct snd_sof_dsp_ops *ops; int ret; @@ -55,6 +105,8 @@ static int sof_of_probe(struct platform_device *pdev) if (!sof_pdata) return -ENOMEM; + platform_set_drvdata(pdev, sof_pdata); + desc = device_get_match_data(dev); if (!desc) return -ENODEV; @@ -72,18 +124,17 @@ static int sof_of_probe(struct platform_device *pdev) mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL); if (!mach) return -ENOMEM; - ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops); + + mach->drv_name = "sof-nocodec"; + sof_pdata->fw_filename = desc->nocodec_fw_filename; + sof_pdata->tplg_filename = desc->nocodec_tplg_filename; + ret = sof_nocodec_setup(dev, ops); if (ret < 0) return ret; -#else - /* TODO: implement case where we actually have a codec */ - return -ENODEV; #endif - if (mach) - mach->mach_params.platform = dev_name(dev); - - sof_pdata->machine = mach; + /* TODO: replace machine with info from DT */ + sof_pdata->machine = NULL; sof_pdata->desc = desc; sof_pdata->dev = &pdev->dev; sof_pdata->platform = dev_name(dev); @@ -92,6 +143,16 @@ static int sof_of_probe(struct platform_device *pdev) sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path; sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path; + ret = sof_of_parse(pdev); + if (ret < 0) { + dev_err(dev, "Could not parse SOF OF DSP node\n"); + return ret; + } + + /* use default fw filename if none provided in DT */ + if (!sof_pdata->fw_filename) + sof_pdata->fw_filename = desc->default_fw_filename; + #if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) /* set callback to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_of_probe_complete; @@ -123,7 +184,12 @@ static int sof_of_remove(struct platform_device *pdev) static const struct of_device_id sof_of_ids[] = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) { .compatible = "fsl,imx8qxp-dsp", .data = &sof_of_imx8qxp_desc}, + { .compatible = "fsl,imx8qm-dsp", .data = &sof_of_imx8qm_desc}, #endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M) + { .compatible = "fsl,imx8mp-dsp", .data = &sof_of_imx8mp_desc}, +#endif + { } }; MODULE_DEVICE_TABLE(of, sof_of_ids); diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index 3f79cd03507c..6b6663744b04 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -40,6 +40,7 @@ static const struct sof_dev_desc bxt_desc = { .chip_info = &apl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-apl.ri", .nocodec_fw_filename = "sof-apl.ri", .nocodec_tplg_filename = "sof-apl-nocodec.tplg", .ops = &sof_apl_ops, @@ -58,6 +59,7 @@ static const struct sof_dev_desc glk_desc = { .chip_info = &apl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-glk.ri", .nocodec_fw_filename = "sof-glk.ri", .nocodec_tplg_filename = "sof-glk-nocodec.tplg", .ops = &sof_apl_ops, @@ -86,6 +88,7 @@ static const struct sof_dev_desc tng_desc = { .chip_info = &tng_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-byt.ri", .nocodec_fw_filename = "sof-byt.ri", .nocodec_tplg_filename = "sof-byt.tplg", .ops = &sof_tng_ops, @@ -104,6 +107,7 @@ static const struct sof_dev_desc cnl_desc = { .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-cnl.ri", .nocodec_fw_filename = "sof-cnl.ri", .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, @@ -113,7 +117,7 @@ static const struct sof_dev_desc cnl_desc = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE) static const struct sof_dev_desc cfl_desc = { - .machines = snd_soc_acpi_intel_cnl_machines, + .machines = snd_soc_acpi_intel_cfl_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -122,7 +126,8 @@ static const struct sof_dev_desc cfl_desc = { .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-cnl.ri", + .default_fw_filename = "sof-cfl.ri", + .nocodec_fw_filename = "sof-cfl.ri", .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, .arch_ops = &sof_xtensa_arch_ops @@ -133,7 +138,7 @@ static const struct sof_dev_desc cfl_desc = { IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE_H) static const struct sof_dev_desc cml_desc = { - .machines = snd_soc_acpi_intel_cnl_machines, + .machines = snd_soc_acpi_intel_cml_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -142,7 +147,8 @@ static const struct sof_dev_desc cml_desc = { .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", - .nocodec_fw_filename = "sof-cnl.ri", + .default_fw_filename = "sof-cml.ri", + .nocodec_fw_filename = "sof-cml.ri", .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, .arch_ops = &sof_xtensa_arch_ops @@ -160,6 +166,7 @@ static const struct sof_dev_desc icl_desc = { .chip_info = &icl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-icl.ri", .nocodec_fw_filename = "sof-icl.ri", .nocodec_tplg_filename = "sof-icl-nocodec.tplg", .ops = &sof_cnl_ops, @@ -214,6 +221,7 @@ static const struct sof_dev_desc tgl_desc = { .chip_info = &tgl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-tgl.ri", .nocodec_fw_filename = "sof-tgl.ri", .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", .ops = &sof_cnl_ops, @@ -232,6 +240,7 @@ static const struct sof_dev_desc ehl_desc = { .chip_info = &ehl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", + .default_fw_filename = "sof-ehl.ri", .nocodec_fw_filename = "sof-ehl.ri", .nocodec_tplg_filename = "sof-ehl-nocodec.tplg", .ops = &sof_cnl_ops, @@ -306,7 +315,10 @@ static int sof_pci_probe(struct pci_dev *pci, ret = -ENOMEM; goto release_regions; } - ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops); + mach->drv_name = "sof-nocodec"; + sof_pdata->fw_filename = desc->nocodec_fw_filename; + sof_pdata->tplg_filename = desc->nocodec_tplg_filename; + ret = sof_nocodec_setup(dev, ops); if (ret < 0) goto release_regions; diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 7b329bd99674..f04550c7ca09 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -202,9 +202,19 @@ struct snd_sof_dsp_ops { int (*get_window_offset)(struct snd_sof_dev *sdev, u32 id);/* mandatory for common loader code */ + /* machine driver ops */ + int (*machine_register)(struct snd_sof_dev *sdev, + void *pdata); /* optional */ + void (*machine_unregister)(struct snd_sof_dev *sdev, + void *pdata); /* optional */ + int (*machine_check)(struct snd_sof_dev *sdev); /* optional */ + /* DAI ops */ struct snd_soc_dai_driver *drv; int num_drv; + + /* ALSA HW info flags, will be stored in snd_pcm_runtime.hw.info */ + u32 hw_info; }; /* DSP architecture specific callbacks for oops and stack dumps */ @@ -414,6 +424,7 @@ struct snd_sof_dev { struct snd_dma_buffer dmab_bdl; struct sof_ipc_fw_ready fw_ready; struct sof_ipc_fw_version fw_version; + struct sof_ipc_cc_version *cc_version; /* topology */ struct snd_soc_tplg_ops *tplg_ops; @@ -467,10 +478,14 @@ int snd_sof_suspend(struct device *dev); void snd_sof_new_platform_drv(struct snd_sof_dev *sdev); -int snd_sof_create_page_table(struct snd_sof_dev *sdev, +int snd_sof_create_page_table(struct device *dev, struct snd_dma_buffer *dmab, unsigned char *page_table, size_t size); +int sof_machine_register(struct snd_sof_dev *sdev, void *pdata); +void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata); +int sof_machine_check(struct snd_sof_dev *sdev); + /* * Firmware loading. */ @@ -551,8 +566,6 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_ipc *ipc, * There is no snd_sof_free_topology since topology components will * be freed by snd_soc_unregister_component, */ -int snd_sof_init_topology(struct snd_sof_dev *sdev, - struct snd_soc_tplg_ops *ops); int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file); int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index fa299e078156..bbe9069e8464 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -406,6 +406,7 @@ static const struct sof_process_types sof_process[] = { {"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR}, {"MUX", SOF_PROCESS_MUX, SOF_COMP_MUX}, {"DEMUX", SOF_PROCESS_DEMUX, SOF_COMP_DEMUX}, + {"POST_PROCESS", SOF_PROCESS_PP, SOF_COMP_PP}, }; static enum sof_ipc_process_type find_process(const char *name) @@ -799,6 +800,21 @@ static const struct sof_topology_token dmic_tokens[] = { }; +/* ESAI */ +static const struct sof_topology_token esai_tokens[] = { + {SOF_TKN_IMX_ESAI_MCLK_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_esai_params, mclk_id), 0}, +}; + +/* SAI */ +static const struct sof_topology_token sai_tokens[] = { + {SOF_TKN_IMX_SAI_MCLK_ID, + SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_sai_params, mclk_id), 0}, +}; + + /* * DMIC PDM Tokens * SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token @@ -2526,8 +2542,66 @@ static int sof_link_sai_load(struct snd_soc_component *scomp, int index, struct snd_soc_tplg_hw_config *hw_config, struct sof_ipc_dai_config *config) { - /*TODO: Add implementation */ - return 0; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->sai, 0, sizeof(struct sof_ipc_dai_sai_params)); + config->hdr.size = size; + + ret = sof_parse_tokens(scomp, &config->sai, sai_tokens, + ARRAY_SIZE(sai_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse sai tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + config->sai.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->sai.mclk_direction = hw_config->mclk_direction; + + config->sai.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->sai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->sai.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->sai.tx_slots = le32_to_cpu(hw_config->tx_slots); + + dev_info(sdev->dev, + "tplg: config SAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n", + config->dai_index, config->format, + config->sai.mclk_rate, config->sai.tdm_slot_width, + config->sai.tdm_slots, config->sai.mclk_id); + + if (config->sai.tdm_slots < 1 || config->sai.tdm_slots > 8) { + dev_err(sdev->dev, "error: invalid channel count for SAI%d\n", + config->dai_index); + return -EINVAL; + } + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for SAI%d\n", + config->dai_index); + return ret; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + dev_err(sdev->dev, "error: failed to save DAI config for SAI%d\n", + config->dai_index); + + return ret; } static int sof_link_esai_load(struct snd_soc_component *scomp, int index, @@ -2536,8 +2610,66 @@ static int sof_link_esai_load(struct snd_soc_component *scomp, int index, struct snd_soc_tplg_hw_config *hw_config, struct sof_ipc_dai_config *config) { - /*TODO: Add implementation */ - return 0; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_private *private = &cfg->priv; + struct sof_ipc_reply reply; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->esai, 0, sizeof(struct sof_ipc_dai_esai_params)); + config->hdr.size = size; + + ret = sof_parse_tokens(scomp, &config->esai, esai_tokens, + ARRAY_SIZE(esai_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(sdev->dev, "error: parse esai tokens failed %d\n", + le32_to_cpu(private->size)); + return ret; + } + + config->esai.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->esai.bclk_rate = le32_to_cpu(hw_config->bclk_rate); + config->esai.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->esai.mclk_direction = hw_config->mclk_direction; + config->esai.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->esai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->esai.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->esai.tx_slots = le32_to_cpu(hw_config->tx_slots); + + dev_info(sdev->dev, + "tplg: config ESAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n", + config->dai_index, config->format, + config->esai.mclk_rate, config->esai.tdm_slot_width, + config->esai.tdm_slots, config->esai.mclk_id); + + if (config->esai.tdm_slots < 1 || config->esai.tdm_slots > 8) { + dev_err(sdev->dev, "error: invalid channel count for ESAI%d\n", + config->dai_index); + return -EINVAL; + } + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config->hdr.cmd, config, size, &reply, + sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to set DAI config for ESAI%d\n", + config->dai_index); + return ret; + } + + /* set config for all DAI's with name matching the link name */ + ret = sof_set_dai_config(sdev, size, link, config); + if (ret < 0) + dev_err(sdev->dev, "error: failed to save DAI config for ESAI%d\n", + config->dai_index); + + return ret; } static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, @@ -2972,7 +3104,9 @@ found: case SOF_DAI_INTEL_SSP: case SOF_DAI_INTEL_DMIC: case SOF_DAI_INTEL_ALH: - /* no resource needs to be released for SSP, DMIC and ALH */ + case SOF_DAI_IMX_SAI: + case SOF_DAI_IMX_ESAI: + /* no resource needs to be released for all cases above */ break; case SOF_DAI_INTEL_HDA: ret = sof_link_hda_unload(sdev, link); @@ -3303,15 +3437,6 @@ static struct snd_soc_tplg_ops sof_tplg_ops = { .bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops), }; -int snd_sof_init_topology(struct snd_sof_dev *sdev, - struct snd_soc_tplg_ops *ops) -{ - /* TODO: support linked list of topologies */ - sdev->tplg_ops = ops; - return 0; -} -EXPORT_SYMBOL(snd_sof_init_topology); - int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file) { const struct firmware *fw; diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c index fd6f5913782b..a775c6b21b55 100644 --- a/sound/soc/sof/trace.c +++ b/sound/soc/sof/trace.c @@ -244,8 +244,8 @@ int snd_sof_init_trace(struct snd_sof_dev *sdev) } /* create compressed page table for audio firmware */ - ret = snd_sof_create_page_table(sdev, &sdev->dmatb, sdev->dmatp.area, - sdev->dmatb.bytes); + ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb, + sdev->dmatp.area, sdev->dmatb.bytes); if (ret < 0) goto table_err; diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c index 2ac4c3da0320..9831eb57df6c 100644 --- a/sound/soc/sof/utils.c +++ b/sound/soc/sof/utils.c @@ -10,6 +10,7 @@ #include <linux/io-64-nonatomic-lo-hi.h> #include <linux/platform_device.h> +#include <asm/unaligned.h> #include <sound/soc.h> #include <sound/sof.h> #include "sof-priv.h" @@ -110,3 +111,62 @@ void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, memcpy_fromio(dest, src, size); } EXPORT_SYMBOL(sof_block_read); + +/* + * Generic buffer page table creation. + * Take the each physical page address and drop the least significant unused + * bits from each (based on PAGE_SIZE). Then pack valid page address bits + * into compressed page table. + */ + +int snd_sof_create_page_table(struct device *dev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size) +{ + int i, pages; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", + dmab->area, size, pages); + + for (i = 0; i < pages; i++) { + /* + * The number of valid address bits for each page is 20. + * idx determines the byte position within page_table + * where the current page's address is stored + * in the compressed page_table. + * This can be calculated by multiplying the page number by 2.5. + */ + u32 idx = (5 * i) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u8 *pg_table; + + dev_vdbg(dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u8 *)(page_table + idx); + + /* + * pagetable compression: + * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 + * ___________pfn 0__________ __________pfn 1___________ _pfn 2... + * .... .... .... .... .... .... .... .... .... .... .... + * It is created by: + * 1. set current location to 0, PFN index i to 0 + * 2. put pfn[i] at current location in Little Endian byte order + * 3. calculate an intermediate value as + * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) + * 4. put x at offset (current location + 2) in LE byte order + * 5. increment current location by 5 bytes, increment i by 2 + * 6. continue to (2) + */ + if (i & 1) + put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, + pg_table); + else + put_unaligned_le32(pfn, pg_table); + } + + return pages; +} +EXPORT_SYMBOL(snd_sof_create_page_table); diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c index f439e5503a3c..7448015a4935 100644 --- a/sound/soc/sprd/sprd-mcdt.c +++ b/sound/soc/sprd/sprd-mcdt.c @@ -959,8 +959,10 @@ static int sprd_mcdt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, mcdt); irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get MCDT interrupt\n"); return irq; + } ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler, 0, "sprd-mcdt", mcdt); diff --git a/sound/soc/sti/sti_uniperif.c b/sound/soc/sti/sti_uniperif.c index ee4a0151e63e..645bcbe91601 100644 --- a/sound/soc/sti/sti_uniperif.c +++ b/sound/soc/sti/sti_uniperif.c @@ -426,8 +426,10 @@ static int sti_uniperiph_cpu_dai_of(struct device_node *node, UNIPERIF_FIFO_DATA_OFFSET(uni); uni->irq = platform_get_irq(priv->pdev, 0); - if (uni->irq < 0) + if (uni->irq < 0) { + dev_err(dev, "Failed to get IRQ resource\n"); return -ENXIO; + } uni->type = dev_data->type; diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c index 3e7226a53e53..ba6452dab69b 100644 --- a/sound/soc/stm/stm32_i2s.c +++ b/sound/soc/stm/stm32_i2s.c @@ -855,8 +855,11 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, /* Get irqs */ irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + if (irq != -EPROBE_DEFER) + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); return irq; + } ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, IRQF_ONESHOT, dev_name(&pdev->dev), i2s); diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c index ef4273361d0d..1ac5103cea78 100644 --- a/sound/soc/stm/stm32_sai.c +++ b/sound/soc/stm/stm32_sai.c @@ -193,8 +193,10 @@ static int stm32_sai_probe(struct platform_device *pdev) /* init irqs */ sai->irq = platform_get_irq(pdev, 0); - if (sai->irq < 0) + if (sai->irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); return sai->irq; + } /* reset */ rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index 9fc2a1767eb1..55a55179d0f1 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -920,8 +920,10 @@ static int stm32_spdifrx_parse_of(struct platform_device *pdev, } spdifrx->irq = platform_get_irq(pdev, 0); - if (spdifrx->irq < 0) + if (spdifrx->irq < 0) { + dev_err(&pdev->dev, "No irq for node %s\n", pdev->name); return spdifrx->irq; + } return 0; } diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index 9655dec4166b..203dd48114b1 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -1198,8 +1198,10 @@ static int sun4i_i2s_probe(struct platform_device *pdev) return PTR_ERR(regs); irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + dev_err(&pdev->dev, "Can't retrieve our interrupt\n"); return irq; + } i2s->variant = of_device_get_match_data(&pdev->dev); if (!i2s->variant) { diff --git a/sound/soc/uniphier/aio-dma.c b/sound/soc/uniphier/aio-dma.c index e8446cc4e8f8..862346d66774 100644 --- a/sound/soc/uniphier/aio-dma.c +++ b/sound/soc/uniphier/aio-dma.c @@ -289,8 +289,10 @@ int uniphier_aiodma_soc_register_platform(struct platform_device *pdev) return PTR_ERR(chip->regmap); irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + dev_err(dev, "Could not get irq.\n"); return irq; + } ret = devm_request_irq(dev, irq, aiodma_irq, IRQF_SHARED, dev_name(dev), pdev); diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c index 1f15c11782ec..d0e22dafadeb 100644 --- a/sound/soc/xilinx/xlnx_formatter_pcm.c +++ b/sound/soc/xilinx/xlnx_formatter_pcm.c @@ -613,6 +613,7 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev) aud_drv_data->mm2s_irq = platform_get_irq_byname(pdev, "irq_mm2s"); if (aud_drv_data->mm2s_irq < 0) { + dev_err(dev, "xlnx audio mm2s irq resource failed\n"); ret = aud_drv_data->mm2s_irq; goto clk_err; } @@ -639,6 +640,7 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev) aud_drv_data->s2mm_irq = platform_get_irq_byname(pdev, "irq_s2mm"); if (aud_drv_data->s2mm_irq < 0) { + dev_err(dev, "xlnx audio s2mm irq resource failed\n"); ret = aud_drv_data->s2mm_irq; goto clk_err; } diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c index efd374f114a0..9da395d14a8d 100644 --- a/sound/soc/xtensa/xtfpga-i2s.c +++ b/sound/soc/xtensa/xtfpga-i2s.c @@ -570,6 +570,7 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) { + dev_err(&pdev->dev, "No IRQ resource\n"); err = irq; goto err; } |