diff options
author | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:00:43 +0800 |
---|---|---|
committer | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:00:43 +0800 |
commit | d7ba9dc82699535ee36e2cf8f10d992f2a5bddae (patch) | |
tree | 9e62a5a63e486cd205be35d7dd14342462b422a8 /sound | |
parent | f543f63f55e93400048dc2d31c4356d3ffc9a34d (diff) | |
parent | dfb874e95e3aa46b43b8940b6e921cf753eda947 (diff) |
Merge remote-tracking branch 'origin/audio/rpmsg' into audio/next
* origin/audio/rpmsg: (59 commits)
LF-215: ASoC: fsl_rpmsg_i2s: Enable WQ_FREEZABLE for workqueue
ASoC: imx-pcm-rpmsg: Remove the prtd
ASoC: imx-pcm-rpmsg: Fix writecombine/wc build error
ASoC: rpmsg_ak4497: ignore suspend with DAPM
Revert "ASoC: soc-pcm: remove soc_rtdcom_ack()"
...
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/codecs/Kconfig | 11 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 7 | ||||
-rw-r--r-- | sound/soc/codecs/rpmsg_ak4497.c | 1110 | ||||
-rw-r--r-- | sound/soc/codecs/rpmsg_ak4497.h | 90 | ||||
-rw-r--r-- | sound/soc/codecs/rpmsg_cs42xx8.c | 752 | ||||
-rw-r--r-- | sound/soc/codecs/rpmsg_cs42xx8.h | 232 | ||||
-rw-r--r-- | sound/soc/codecs/rpmsg_wm8960.c | 1542 | ||||
-rw-r--r-- | sound/soc/fsl/Kconfig | 26 | ||||
-rw-r--r-- | sound/soc/fsl/Makefile | 5 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_rpmsg_i2s.c | 425 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_rpmsg_i2s.h | 454 | ||||
-rw-r--r-- | sound/soc/fsl/imx-pcm-rpmsg.c | 893 | ||||
-rw-r--r-- | sound/soc/fsl/imx-rpmsg.c | 175 | ||||
-rw-r--r-- | sound/soc/soc-pcm.c | 22 |
14 files changed, 5744 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5fda1c26bf1d..f128ffabf5e5 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -257,6 +257,9 @@ 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_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 @@ -1439,6 +1442,14 @@ config SND_SOC_ZX_AUD96P22 depends on I2C select REGMAP_I2C +config SND_SOC_RPMSG_WM8960 + tristate + +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 8f71ebb862e2..58c2b3cec86f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -275,6 +275,10 @@ 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-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 @@ -559,6 +563,9 @@ 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_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/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..d70e5a9b0606 --- /dev/null +++ b/sound/soc/codecs/rpmsg_wm8960.c @@ -0,0 +1,1542 @@ +/* + * 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/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" + +/* 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 + +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 }, +}; + +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; +}; + +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; + } +} + +#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0) + +/* 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, +}; + +static 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, +}; + +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); + + 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; +} + +static const struct snd_soc_component_driver rpmsg_wm8960_component = { + .probe = rpmsg_wm8960_probe, + .set_bias_level = wm8960_set_bias_level, + .suspend_bias_off = 1, +}; + +static const 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, +}; + +#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/fsl/Kconfig b/sound/soc/fsl/Kconfig index bf044d45fef3..0a887b373b6f 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -90,6 +90,13 @@ config SND_SOC_FSL_DSP 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 HAVE_IMX_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. @@ -103,6 +110,11 @@ config SND_SOC_IMX_PCM_DMA tristate select SND_SOC_GENERIC_DMAENGINE_PCM +config SND_SOC_IMX_PCM_RPMSG + tristate + depends on HAVE_IMX_RPMSG + select SND_SOC_GENERIC_DMAENGINE_PCM + config SND_SOC_IMX_AUDMUX tristate "Digital Audio Mux module support" help @@ -405,6 +417,20 @@ config SND_SOC_IMX_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 HAVE_IMX_RPMSG + select SND_SOC_IMX_PCM_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) diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index c989f8b74d5d..8b40b170062b 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -29,6 +29,7 @@ snd-soc-fsl-easrc-objs := fsl_easrc.o fsl_easrc_dma.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 @@ -43,6 +44,7 @@ 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 # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o @@ -63,6 +65,7 @@ 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 # i.MX Machine Support snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o @@ -90,6 +93,7 @@ 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 obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o @@ -103,6 +107,7 @@ 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 diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.c b/sound/soc/fsl/fsl_rpmsg_i2s.c new file mode 100644 index 000000000000..f392817dd3fc --- /dev/null +++ b/sound/soc/fsl/fsl_rpmsg_i2s.c @@ -0,0 +1,425 @@ +/* + * 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"}, + { /* 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_KNOT; + 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; + + flush_workqueue(i2s_info->rpmsg_wq); + 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..e42986ca14ff --- /dev/null +++ b/sound/soc/fsl/fsl_rpmsg_i2s.h @@ -0,0 +1,454 @@ +/* + * 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 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/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c new file mode 100644 index 000000000000..8da1345ccd06 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -0,0 +1,893 @@ +/* + * 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/rpmsg.h> +#include <linux/imx_rpmsg.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; + +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); + +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_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"); diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c new file mode 100644 index 000000000000..9db5f190bc4f --- /dev/null +++ b/sound/soc/fsl/imx-rpmsg.c @@ -0,0 +1,175 @@ +/* + * 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/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 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; + } + + rpmsg_i2s = platform_get_drvdata(cpu_pdev); + + 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_CBM_CFM; + + if (rpmsg_i2s->codec_wm8960) { + 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/soc-pcm.c b/sound/soc/soc-pcm.c index 0eed248a1c4b..c2a78174f249 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2818,6 +2818,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) { @@ -2941,6 +2961,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) |