summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/sound/starfive,jh7110-pwmdac.yaml76
-rw-r--r--MAINTAINERS7
-rw-r--r--include/sound/cs35l41.h4
-rw-r--r--include/sound/soc.h7
-rw-r--r--sound/pci/hda/cs35l41_hda.c23
-rw-r--r--sound/soc/amd/acp-config.c70
-rw-r--r--sound/soc/amd/acp/Makefile2
-rw-r--r--sound/soc/amd/acp/acp-legacy-mach.c102
-rw-r--r--sound/soc/amd/acp/acp-mach-common.c8
-rw-r--r--sound/soc/amd/acp/acp-mach.h67
-rw-r--r--sound/soc/amd/acp/acp-renoir.c4
-rw-r--r--sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c443
-rw-r--r--sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.h12
-rw-r--r--sound/soc/codecs/ak4642.c28
-rw-r--r--sound/soc/codecs/cs35l41-i2c.c11
-rw-r--r--sound/soc/codecs/cs35l41-lib.c60
-rw-r--r--sound/soc/codecs/cs35l41-spi.c11
-rw-r--r--sound/soc/codecs/cs35l41.c93
-rw-r--r--sound/soc/codecs/cs35l41.h1
-rw-r--r--sound/soc/codecs/cs35l45-tables.c3
-rw-r--r--sound/soc/codecs/cs35l45.c193
-rw-r--r--sound/soc/codecs/cs35l45.h36
-rw-r--r--sound/soc/codecs/cs42xx8-i2c.c14
-rw-r--r--sound/soc/codecs/es8316.c52
-rw-r--r--sound/soc/codecs/es8316.h3
-rw-r--r--sound/soc/codecs/tas571x.c13
-rw-r--r--sound/soc/codecs/tlv320aic32x4-i2c.c19
-rw-r--r--sound/soc/codecs/tlv320aic32x4-spi.c18
-rw-r--r--sound/soc/codecs/tlv320aic32x4.c5
-rw-r--r--sound/soc/codecs/tlv320aic32x4.h3
-rw-r--r--sound/soc/codecs/wm8580.c26
-rw-r--r--sound/soc/mediatek/common/mtk-dsp-sof-common.c113
-rw-r--r--sound/soc/mediatek/common/mtk-dsp-sof-common.h8
-rw-r--r--sound/soc/mediatek/mt8188/mt8188-mt6359.c239
-rw-r--r--sound/soc/meson/axg-fifo.c2
-rw-r--r--sound/soc/meson/axg-fifo.h2
-rw-r--r--sound/soc/meson/axg-tdm.h2
-rw-r--r--sound/soc/starfive/Kconfig9
-rw-r--r--sound/soc/starfive/Makefile1
-rw-r--r--sound/soc/starfive/jh7110_pwmdac.c529
40 files changed, 2062 insertions, 257 deletions
diff --git a/Documentation/devicetree/bindings/sound/starfive,jh7110-pwmdac.yaml b/Documentation/devicetree/bindings/sound/starfive,jh7110-pwmdac.yaml
new file mode 100644
index 000000000000..e2b4db6aa2fb
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/starfive,jh7110-pwmdac.yaml
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/starfive,jh7110-pwmdac.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: StarFive JH7110 PWM-DAC Controller
+
+description:
+ The PWM-DAC Controller uses PWM square wave generators plus RC filters to
+ form a DAC for audio play in StarFive JH7110 SoC. This audio play controller
+ supports 16 bit audio format, up to 48K sampling frequency, up to left and
+ right dual channels.
+
+maintainers:
+ - Hal Feng <hal.feng@starfivetech.com>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: starfive,jh7110-pwmdac
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: PWMDAC APB
+ - description: PWMDAC CORE
+
+ clock-names:
+ items:
+ - const: apb
+ - const: core
+
+ resets:
+ maxItems: 1
+ description: PWMDAC APB
+
+ dmas:
+ maxItems: 1
+ description: TX DMA Channel
+
+ dma-names:
+ const: tx
+
+ "#sound-dai-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - resets
+ - dmas
+ - dma-names
+ - "#sound-dai-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ pwmdac@100b0000 {
+ compatible = "starfive,jh7110-pwmdac";
+ reg = <0x100b0000 0x1000>;
+ clocks = <&syscrg 157>,
+ <&syscrg 158>;
+ clock-names = "apb", "core";
+ resets = <&syscrg 96>;
+ dmas = <&dma 22>;
+ dma-names = "tx";
+ #sound-dai-cells = <0>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 90f13281d297..03efb4b659fa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20466,6 +20466,13 @@ S: Supported
F: Documentation/devicetree/bindings/clock/starfive,jh7110-pll.yaml
F: drivers/clk/starfive/clk-starfive-jh7110-pll.c
+STARFIVE JH7110 PWMDAC DRIVER
+M: Hal Feng <hal.feng@starfivetech.com>
+M: Xingyu Wu <xingyu.wu@starfivetech.com>
+S: Supported
+F: Documentation/devicetree/bindings/sound/starfive,jh7110-pwmdac.yaml
+F: sound/soc/starfive/jh7110_pwmdac.c
+
STARFIVE JH7110 SYSCON
M: William Qiu <william.qiu@starfivetech.com>
M: Xingyu Wu <xingyu.wu@starfivetech.com>
diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h
index 1bf757901d02..2fe8c6b0d4cf 100644
--- a/include/sound/cs35l41.h
+++ b/include/sound/cs35l41.h
@@ -11,7 +11,6 @@
#define __CS35L41_H
#include <linux/regmap.h>
-#include <linux/completion.h>
#include <linux/firmware/cirrus/cs_dsp.h>
#define CS35L41_FIRSTREG 0x00000000
@@ -902,7 +901,8 @@ int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap);
int cs35l41_init_boost(struct device *dev, struct regmap *regmap,
struct cs35l41_hw_cfg *hw_cfg);
bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type);
+int cs35l41_mdsync_up(struct regmap *regmap);
int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type,
- int enable, struct completion *pll_lock, bool firmware_running);
+ int enable, bool firmware_running);
#endif /* __CS35L41_H */
diff --git a/include/sound/soc.h b/include/sound/soc.h
index fa2337a3cf4c..509386ff5212 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -10,6 +10,7 @@
#ifndef __LINUX_SND_SOC_H
#define __LINUX_SND_SOC_H
+#include <linux/args.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/types.h>
@@ -870,12 +871,8 @@ asoc_link_to_platform(struct snd_soc_dai_link *link, int n) {
.platforms = platform, \
.num_platforms = ARRAY_SIZE(platform)
-#define SND_SOC_DAILINK_REGx(_1, _2, _3, func, ...) func
#define SND_SOC_DAILINK_REG(...) \
- SND_SOC_DAILINK_REGx(__VA_ARGS__, \
- SND_SOC_DAILINK_REG3, \
- SND_SOC_DAILINK_REG2, \
- SND_SOC_DAILINK_REG1)(__VA_ARGS__)
+ CONCATENATE(SND_SOC_DAILINK_REG, COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)
#define SND_SOC_DAILINK_DEF(name, def...) \
static struct snd_soc_dai_link_component name[] = { def }
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index f9b77353c266..c74faa2ff46c 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -527,7 +527,7 @@ static void cs35l41_hda_play_done(struct device *dev)
dev_dbg(dev, "Play (Complete)\n");
- cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL,
+ cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1,
cs35l41->firmware_running);
if (cs35l41->firmware_running) {
regmap_multi_reg_write(reg, cs35l41_hda_unmute_dsp,
@@ -546,7 +546,7 @@ static void cs35l41_hda_pause_start(struct device *dev)
dev_dbg(dev, "Pause (Start)\n");
regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute));
- cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL,
+ cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0,
cs35l41->firmware_running);
}
@@ -1550,27 +1550,27 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, int_status,
int_status & CS35L41_OTP_BOOT_DONE, 1000, 100000);
if (ret) {
- dev_err(cs35l41->dev, "Failed waiting for OTP_BOOT_DONE: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "Failed waiting for OTP_BOOT_DONE\n");
goto err;
}
ret = regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_sts);
if (ret || (int_sts & CS35L41_OTP_BOOT_ERR)) {
- dev_err(cs35l41->dev, "OTP Boot status %x error: %d\n",
- int_sts & CS35L41_OTP_BOOT_ERR, ret);
+ dev_err_probe(cs35l41->dev, ret, "OTP Boot status %x error\n",
+ int_sts & CS35L41_OTP_BOOT_ERR);
ret = -EIO;
goto err;
}
ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, &regid);
if (ret) {
- dev_err(cs35l41->dev, "Get Device ID failed: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "Get Device ID failed\n");
goto err;
}
ret = regmap_read(cs35l41->regmap, CS35L41_REVID, &reg_revid);
if (ret) {
- dev_err(cs35l41->dev, "Get Revision ID failed: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "Get Revision ID failed\n");
goto err;
}
@@ -1593,7 +1593,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap);
if (ret) {
- dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "OTP Unpack failed\n");
goto err;
}
@@ -1624,9 +1624,8 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
ret = component_add(cs35l41->dev, &cs35l41_hda_comp_ops);
if (ret) {
- dev_err(cs35l41->dev, "Register component failed: %d\n", ret);
- pm_runtime_disable(cs35l41->dev);
- goto err;
+ dev_err_probe(cs35l41->dev, ret, "Register component failed\n");
+ goto err_pm;
}
dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n", regid, reg_revid);
@@ -1634,6 +1633,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
return 0;
err_pm:
+ pm_runtime_dont_use_autosuspend(cs35l41->dev);
pm_runtime_disable(cs35l41->dev);
pm_runtime_put_noidle(cs35l41->dev);
@@ -1652,6 +1652,7 @@ void cs35l41_hda_remove(struct device *dev)
struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
pm_runtime_get_sync(cs35l41->dev);
+ pm_runtime_dont_use_autosuspend(cs35l41->dev);
pm_runtime_disable(cs35l41->dev);
if (cs35l41->halo_initialized)
diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c
index f27c27580009..a58d646d28f6 100644
--- a/sound/soc/amd/acp-config.c
+++ b/sound/soc/amd/acp-config.c
@@ -61,6 +61,76 @@ static const struct config_entry config_table[] = {
{}
},
},
+ {
+ .flags = FLAG_AMD_LEGACY,
+ .device = ACP_PCI_DEV_ID,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXXW"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+ },
+ },
+ {}
+ },
+ },
+ {
+ .flags = FLAG_AMD_LEGACY,
+ .device = ACP_PCI_DEV_ID,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXX9"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+ },
+ },
+ {}
+ },
+ },
+ {
+ .flags = FLAG_AMD_LEGACY,
+ .device = ACP_PCI_DEV_ID,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "BOM-WXX9"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+ },
+ },
+ {}
+ },
+ },
+ {
+ .flags = FLAG_AMD_LEGACY,
+ .device = ACP_PCI_DEV_ID,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1020"),
+ },
+ },
+ {}
+ },
+ },
+ {
+ .flags = FLAG_AMD_LEGACY,
+ .device = ACP_PCI_DEV_ID,
+ .dmi_table = (const struct dmi_system_id []) {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1040"),
+ },
+ },
+ {}
+ },
+ },
};
int snd_amd_acp_find_config(struct pci_dev *pci)
diff --git a/sound/soc/amd/acp/Makefile b/sound/soc/amd/acp/Makefile
index 4e65fdbc8dca..dc70691bc293 100644
--- a/sound/soc/amd/acp/Makefile
+++ b/sound/soc/amd/acp/Makefile
@@ -17,7 +17,7 @@ snd-acp-rembrandt-objs := acp-rembrandt.o
#machine specific driver
snd-acp-mach-objs := acp-mach-common.o
-snd-acp-legacy-mach-objs := acp-legacy-mach.o
+snd-acp-legacy-mach-objs := acp-legacy-mach.o acp3x-es83xx/acp3x-es83xx.o
snd-acp-sof-mach-objs := acp-sof-mach.o
obj-$(CONFIG_SND_SOC_AMD_ACP_PCM) += snd-acp-pcm.o
diff --git a/sound/soc/amd/acp/acp-legacy-mach.c b/sound/soc/amd/acp/acp-legacy-mach.c
index 6d57d17ddfd7..1ab3edffe0ce 100644
--- a/sound/soc/amd/acp/acp-legacy-mach.c
+++ b/sound/soc/amd/acp/acp-legacy-mach.c
@@ -20,6 +20,7 @@
#include <linux/module.h>
#include "acp-mach.h"
+#include "acp3x-es83xx/acp3x-es83xx.h"
static struct acp_card_drvdata rt5682_rt1019_data = {
.hs_cpu_id = I2S_SP,
@@ -51,6 +52,14 @@ static struct acp_card_drvdata rt5682s_rt1019_data = {
.tdm_mode = false,
};
+static struct acp_card_drvdata es83xx_rn_data = {
+ .hs_cpu_id = I2S_SP,
+ .dmic_cpu_id = DMIC,
+ .hs_codec_id = ES83XX,
+ .dmic_codec_id = DMIC,
+ .platform = RENOIR,
+};
+
static struct acp_card_drvdata max_nau8825_data = {
.hs_cpu_id = I2S_HS,
.amp_cpu_id = I2S_HS,
@@ -75,6 +84,39 @@ static struct acp_card_drvdata rt5682s_rt1019_rmb_data = {
.tdm_mode = false,
};
+static bool acp_asoc_init_ops(struct acp_card_drvdata *priv)
+{
+ bool has_ops = false;
+
+ if (priv->hs_codec_id == ES83XX) {
+ has_ops = true;
+ acp3x_es83xx_init_ops(&priv->ops);
+ }
+ return has_ops;
+}
+
+static int acp_asoc_suspend_pre(struct snd_soc_card *card)
+{
+ int ret;
+
+ ret = acp_ops_suspend_pre(card);
+ if (ret == 1)
+ return 0;
+ else
+ return ret;
+}
+
+static int acp_asoc_resume_post(struct snd_soc_card *card)
+{
+ int ret;
+
+ ret = acp_ops_resume_post(card);
+ if (ret == 1)
+ return 0;
+ else
+ return ret;
+}
+
static int acp_asoc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = NULL;
@@ -83,35 +125,68 @@ static int acp_asoc_probe(struct platform_device *pdev)
struct acp_card_drvdata *acp_card_drvdata;
int ret;
- if (!pdev->id_entry)
- return -EINVAL;
+ if (!pdev->id_entry) {
+ ret = -EINVAL;
+ goto out;
+ }
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
- if (!card)
- return -ENOMEM;
+ if (!card) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data;
+ acp_card_drvdata = card->drvdata;
+ acp_card_drvdata->acpi_mach = (struct snd_soc_acpi_mach *)pdev->dev.platform_data;
card->dev = dev;
card->owner = THIS_MODULE;
card->name = pdev->id_entry->name;
- card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data;
- /* Widgets and controls added per-codec in acp-mach-common.c */
- acp_card_drvdata = card->drvdata;
+ acp_asoc_init_ops(card->drvdata);
+
+ /* If widgets and controls are not set in specific callback,
+ * they will be added per-codec in acp-mach-common.c
+ */
+ ret = acp_ops_configure_widgets(card);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Cannot configure widgets for card (%s): %d\n",
+ card->name, ret);
+ goto out;
+ }
+ card->suspend_pre = acp_asoc_suspend_pre;
+ card->resume_post = acp_asoc_resume_post;
+
+ ret = acp_ops_probe(card);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Cannot probe card (%s): %d\n",
+ card->name, ret);
+ goto out;
+ }
+
dmi_id = dmi_first_match(acp_quirk_table);
if (dmi_id && dmi_id->driver_data)
acp_card_drvdata->tdm_mode = dmi_id->driver_data;
- acp_legacy_dai_links_create(card);
+ ret = acp_legacy_dai_links_create(card);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Cannot create dai links for card (%s): %d\n",
+ card->name, ret);
+ goto out;
+ }
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
dev_err(&pdev->dev,
"devm_snd_soc_register_card(%s) failed: %d\n",
card->name, ret);
- return ret;
+ goto out;
}
-
- return 0;
+out:
+ return ret;
}
static const struct platform_device_id board_ids[] = {
@@ -128,6 +203,10 @@ static const struct platform_device_id board_ids[] = {
.driver_data = (kernel_ulong_t)&rt5682s_rt1019_data,
},
{
+ .name = "acp3x-es83xx",
+ .driver_data = (kernel_ulong_t)&es83xx_rn_data,
+ },
+ {
.name = "rmb-nau8825-max",
.driver_data = (kernel_ulong_t)&max_nau8825_data,
},
@@ -153,6 +232,7 @@ MODULE_DESCRIPTION("ACP chrome audio support");
MODULE_ALIAS("platform:acp3xalc56821019");
MODULE_ALIAS("platform:acp3xalc5682sm98360");
MODULE_ALIAS("platform:acp3xalc5682s1019");
+MODULE_ALIAS("platform:acp3x-es83xx");
MODULE_ALIAS("platform:rmb-nau8825-max");
MODULE_ALIAS("platform:rmb-rt5682s-rt1019");
MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c
index a06af82b8056..8f968c12e54a 100644
--- a/sound/soc/amd/acp/acp-mach-common.c
+++ b/sound/soc/amd/acp/acp-mach-common.c
@@ -1513,6 +1513,7 @@ int acp_legacy_dai_links_create(struct snd_soc_card *card)
struct device *dev = card->dev;
struct acp_card_drvdata *drv_data = card->drvdata;
int i = 0, num_links = 0;
+ int rc;
if (drv_data->hs_cpu_id)
num_links++;
@@ -1551,6 +1552,13 @@ int acp_legacy_dai_links_create(struct snd_soc_card *card)
links[i].init = acp_card_rt5682s_init;
links[i].ops = &acp_card_rt5682s_ops;
}
+ if (drv_data->hs_codec_id == ES83XX) {
+ rc = acp_ops_configure_link(card, &links[i]);
+ if (rc != 0) {
+ dev_err(dev, "Failed to configure link for ES83XX: %d\n", rc);
+ return rc;
+ }
+ }
i++;
}
diff --git a/sound/soc/amd/acp/acp-mach.h b/sound/soc/amd/acp/acp-mach.h
index 2b3ec6594023..b0a3f6bd172f 100644
--- a/sound/soc/amd/acp/acp-mach.h
+++ b/sound/soc/amd/acp/acp-mach.h
@@ -20,6 +20,10 @@
#define TDM_CHANNELS 8
+#define ACP_OPS(priv, cb) ((priv)->ops.cb)
+
+#define acp_get_drvdata(card) ((struct acp_card_drvdata *)(card)->drvdata)
+
enum be_id {
HEADSET_BE_ID = 0,
AMP_BE_ID,
@@ -43,6 +47,7 @@ enum codec_endpoints {
NAU8825,
NAU8821,
MAX98388,
+ ES83XX,
};
enum platform_end_point {
@@ -50,6 +55,14 @@ enum platform_end_point {
REMBRANDT,
};
+struct acp_mach_ops {
+ int (*probe)(struct snd_soc_card *card);
+ int (*configure_link)(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link);
+ int (*configure_widgets)(struct snd_soc_card *card);
+ int (*suspend_pre)(struct snd_soc_card *card);
+ int (*resume_post)(struct snd_soc_card *card);
+};
+
struct acp_card_drvdata {
unsigned int hs_cpu_id;
unsigned int amp_cpu_id;
@@ -61,6 +74,9 @@ struct acp_card_drvdata {
unsigned int platform;
struct clk *wclk;
struct clk *bclk;
+ struct acp_mach_ops ops;
+ struct snd_soc_acpi_mach *acpi_mach;
+ void *mach_priv;
bool soc_mclk;
bool tdm_mode;
};
@@ -69,4 +85,55 @@ int acp_sofdsp_dai_links_create(struct snd_soc_card *card);
int acp_legacy_dai_links_create(struct snd_soc_card *card);
extern const struct dmi_system_id acp_quirk_table[];
+static inline int acp_ops_probe(struct snd_soc_card *card)
+{
+ int ret = 1;
+ struct acp_card_drvdata *priv = acp_get_drvdata(card);
+
+ if (ACP_OPS(priv, probe))
+ ret = ACP_OPS(priv, probe)(card);
+ return ret;
+}
+
+static inline int acp_ops_configure_link(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_link)
+{
+ int ret = 1;
+ struct acp_card_drvdata *priv = acp_get_drvdata(card);
+
+ if (ACP_OPS(priv, configure_link))
+ ret = ACP_OPS(priv, configure_link)(card, dai_link);
+ return ret;
+}
+
+static inline int acp_ops_configure_widgets(struct snd_soc_card *card)
+{
+ int ret = 1;
+ struct acp_card_drvdata *priv = acp_get_drvdata(card);
+
+ if (ACP_OPS(priv, configure_widgets))
+ ret = ACP_OPS(priv, configure_widgets)(card);
+ return ret;
+}
+
+static inline int acp_ops_suspend_pre(struct snd_soc_card *card)
+{
+ int ret = 1;
+ struct acp_card_drvdata *priv = acp_get_drvdata(card);
+
+ if (ACP_OPS(priv, suspend_pre))
+ ret = ACP_OPS(priv, suspend_pre)(card);
+ return ret;
+}
+
+static inline int acp_ops_resume_post(struct snd_soc_card *card)
+{
+ int ret = 1;
+ struct acp_card_drvdata *priv = acp_get_drvdata(card);
+
+ if (ACP_OPS(priv, resume_post))
+ ret = ACP_OPS(priv, resume_post)(card);
+ return ret;
+}
+
#endif
diff --git a/sound/soc/amd/acp/acp-renoir.c b/sound/soc/amd/acp/acp-renoir.c
index 54235cad9cc9..b15cbdf7fa9b 100644
--- a/sound/soc/amd/acp/acp-renoir.c
+++ b/sound/soc/amd/acp/acp-renoir.c
@@ -69,6 +69,10 @@ static struct snd_soc_acpi_mach snd_soc_acpi_amd_acp_machines[] = {
.id = "AMDI1019",
.drv_name = "renoir-acp",
},
+ {
+ .id = "ESSX8336",
+ .drv_name = "acp3x-es83xx",
+ },
{},
};
diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c
new file mode 100644
index 000000000000..47ce2f6c74bb
--- /dev/null
+++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Machine driver for AMD ACP Audio engine using ES8336 codec.
+//
+// Copyright 2023 Marian Postevca <posteuca@mutex.one>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <sound/soc-acpi.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include "../acp-mach.h"
+
+#define get_mach_priv(card) ((struct acp3x_es83xx_private *)((acp_get_drvdata(card))->mach_priv))
+
+#define DUAL_CHANNEL 2
+
+#define ES83XX_ENABLE_DMIC BIT(4)
+#define ES83XX_48_MHZ_MCLK BIT(5)
+
+struct acp3x_es83xx_private {
+ bool speaker_on;
+ bool headphone_on;
+ unsigned long quirk;
+ struct snd_soc_component *codec;
+ struct device *codec_dev;
+ struct gpio_desc *gpio_speakers, *gpio_headphone;
+ struct acpi_gpio_params enable_spk_gpio, enable_hp_gpio;
+ struct acpi_gpio_mapping gpio_mapping[3];
+ struct snd_soc_dapm_route mic_map[2];
+};
+
+static const unsigned int channels[] = {
+ DUAL_CHANNEL,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_channels = {
+ .count = ARRAY_SIZE(channels),
+ .list = channels,
+ .mask = 0,
+};
+
+#define ES83xx_12288_KHZ_MCLK_FREQ (48000 * 256)
+#define ES83xx_48_MHZ_MCLK_FREQ (48000 * 1000)
+
+static int acp3x_es83xx_headphone_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+static int acp3x_es83xx_speaker_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+
+static int acp3x_es83xx_codec_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+ struct acp3x_es83xx_private *priv;
+ unsigned int freq;
+ int ret;
+
+ runtime = substream->runtime;
+ rtd = asoc_substream_to_rtd(substream);
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+ priv = get_mach_priv(rtd->card);
+
+ if (priv->quirk & ES83XX_48_MHZ_MCLK) {
+ dev_dbg(priv->codec_dev, "using a 48Mhz MCLK\n");
+ freq = ES83xx_48_MHZ_MCLK_FREQ;
+ } else {
+ dev_dbg(priv->codec_dev, "using a 12.288Mhz MCLK\n");
+ freq = ES83xx_12288_KHZ_MCLK_FREQ;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_OUT);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ runtime->hw.channels_max = DUAL_CHANNEL;
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraints_channels);
+
+ return 0;
+}
+
+static struct snd_soc_jack es83xx_jack;
+
+static struct snd_soc_jack_pin es83xx_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static const struct snd_soc_dapm_widget acp3x_es83xx_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic", NULL),
+
+ SND_SOC_DAPM_SUPPLY("Headphone Power", SND_SOC_NOPM, 0, 0,
+ acp3x_es83xx_headphone_power_event,
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
+ SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0,
+ acp3x_es83xx_speaker_power_event,
+ SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
+};
+
+static const struct snd_soc_dapm_route acp3x_es83xx_audio_map[] = {
+ {"Headphone", NULL, "HPOL"},
+ {"Headphone", NULL, "HPOR"},
+ {"Headphone", NULL, "Headphone Power"},
+
+ /*
+ * There is no separate speaker output instead the speakers are muxed to
+ * the HP outputs. The mux is controlled Speaker and/or headphone switch.
+ */
+ {"Speaker", NULL, "HPOL"},
+ {"Speaker", NULL, "HPOR"},
+ {"Speaker", NULL, "Speaker Power"},
+};
+
+
+static const struct snd_kcontrol_new acp3x_es83xx_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Internal Mic"),
+};
+
+static int acp3x_es83xx_configure_widgets(struct snd_soc_card *card)
+{
+ card->dapm_widgets = acp3x_es83xx_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(acp3x_es83xx_widgets);
+ card->controls = acp3x_es83xx_controls;
+ card->num_controls = ARRAY_SIZE(acp3x_es83xx_controls);
+ card->dapm_routes = acp3x_es83xx_audio_map;
+ card->num_dapm_routes = ARRAY_SIZE(acp3x_es83xx_audio_map);
+
+ return 0;
+}
+
+static int acp3x_es83xx_headphone_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct acp3x_es83xx_private *priv = get_mach_priv(w->dapm->card);
+
+ dev_dbg(priv->codec_dev, "headphone power event = %d\n", event);
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ priv->headphone_on = true;
+ else
+ priv->headphone_on = false;
+
+ gpiod_set_value_cansleep(priv->gpio_speakers, priv->speaker_on);
+ gpiod_set_value_cansleep(priv->gpio_headphone, priv->headphone_on);
+
+ return 0;
+}
+
+static int acp3x_es83xx_speaker_power_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct acp3x_es83xx_private *priv = get_mach_priv(w->dapm->card);
+
+ dev_dbg(priv->codec_dev, "speaker power event: %d\n", event);
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ priv->speaker_on = true;
+ else
+ priv->speaker_on = false;
+
+ gpiod_set_value_cansleep(priv->gpio_speakers, priv->speaker_on);
+ gpiod_set_value_cansleep(priv->gpio_headphone, priv->headphone_on);
+
+ return 0;
+}
+
+static int acp3x_es83xx_suspend_pre(struct snd_soc_card *card)
+{
+ struct acp3x_es83xx_private *priv = get_mach_priv(card);
+
+ /* We need to disable the jack in the machine driver suspend
+ * callback so that the CODEC suspend callback actually gets
+ * called. Without doing it, the CODEC suspend/resume
+ * callbacks do not get called if headphones are plugged in.
+ * This is because plugging in headphones keeps some supplies
+ * active, this in turn means that the lowest bias level
+ * that the CODEC can go to is SND_SOC_BIAS_STANDBY.
+ * If components do not set idle_bias_on to true then
+ * their suspend/resume callbacks do not get called.
+ */
+ dev_dbg(priv->codec_dev, "card suspend\n");
+ snd_soc_component_set_jack(priv->codec, NULL, NULL);
+ return 0;
+}
+
+static int acp3x_es83xx_resume_post(struct snd_soc_card *card)
+{
+ struct acp3x_es83xx_private *priv = get_mach_priv(card);
+
+ /* We disabled jack detection in suspend callback,
+ * enable it back.
+ */
+ dev_dbg(priv->codec_dev, "card resume\n");
+ snd_soc_component_set_jack(priv->codec, &es83xx_jack, NULL);
+ return 0;
+}
+
+static int acp3x_es83xx_configure_gpios(struct acp3x_es83xx_private *priv)
+{
+ int ret = 0;
+
+ priv->enable_spk_gpio.crs_entry_index = 0;
+ priv->enable_hp_gpio.crs_entry_index = 1;
+
+ priv->enable_spk_gpio.active_low = false;
+ priv->enable_hp_gpio.active_low = false;
+
+ priv->gpio_mapping[0].name = "speakers-enable-gpios";
+ priv->gpio_mapping[0].data = &priv->enable_spk_gpio;
+ priv->gpio_mapping[0].size = 1;
+ priv->gpio_mapping[0].quirks = ACPI_GPIO_QUIRK_ONLY_GPIOIO;
+
+ priv->gpio_mapping[1].name = "headphone-enable-gpios";
+ priv->gpio_mapping[1].data = &priv->enable_hp_gpio;
+ priv->gpio_mapping[1].size = 1;
+ priv->gpio_mapping[1].quirks = ACPI_GPIO_QUIRK_ONLY_GPIOIO;
+
+ dev_info(priv->codec_dev, "speaker gpio %d active %s, headphone gpio %d active %s\n",
+ priv->enable_spk_gpio.crs_entry_index,
+ priv->enable_spk_gpio.active_low ? "low" : "high",
+ priv->enable_hp_gpio.crs_entry_index,
+ priv->enable_hp_gpio.active_low ? "low" : "high");
+ return ret;
+}
+
+static int acp3x_es83xx_configure_mics(struct acp3x_es83xx_private *priv)
+{
+ int num_routes = 0;
+ int i;
+
+ if (!(priv->quirk & ES83XX_ENABLE_DMIC)) {
+ priv->mic_map[num_routes].sink = "MIC1";
+ priv->mic_map[num_routes].source = "Internal Mic";
+ num_routes++;
+ }
+
+ priv->mic_map[num_routes].sink = "MIC2";
+ priv->mic_map[num_routes].source = "Headset Mic";
+ num_routes++;
+
+ for (i = 0; i < num_routes; i++)
+ dev_info(priv->codec_dev, "%s is %s\n",
+ priv->mic_map[i].source, priv->mic_map[i].sink);
+
+ return num_routes;
+}
+
+static int acp3x_es83xx_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_component *codec = asoc_rtd_to_codec(runtime, 0)->component;
+ struct snd_soc_card *card = runtime->card;
+ struct acp3x_es83xx_private *priv = get_mach_priv(card);
+ int ret = 0;
+ int num_routes;
+
+ ret = snd_soc_card_jack_new_pins(card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &es83xx_jack, es83xx_jack_pins,
+ ARRAY_SIZE(es83xx_jack_pins));
+ if (ret) {
+ dev_err(card->dev, "jack creation failed %d\n", ret);
+ return ret;
+ }
+
+ snd_jack_set_key(es83xx_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+
+ snd_soc_component_set_jack(codec, &es83xx_jack, NULL);
+
+ priv->codec = codec;
+ acp3x_es83xx_configure_gpios(priv);
+
+ ret = devm_acpi_dev_add_driver_gpios(priv->codec_dev, priv->gpio_mapping);
+ if (ret)
+ dev_warn(priv->codec_dev, "failed to add speaker gpio\n");
+
+ priv->gpio_speakers = gpiod_get_optional(priv->codec_dev, "speakers-enable",
+ priv->enable_spk_gpio.active_low ? GPIOD_OUT_LOW : GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->gpio_speakers)) {
+ dev_err(priv->codec_dev, "could not get speakers-enable GPIO\n");
+ return PTR_ERR(priv->gpio_speakers);
+ }
+
+ priv->gpio_headphone = gpiod_get_optional(priv->codec_dev, "headphone-enable",
+ priv->enable_hp_gpio.active_low ? GPIOD_OUT_LOW : GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->gpio_headphone)) {
+ dev_err(priv->codec_dev, "could not get headphone-enable GPIO\n");
+ return PTR_ERR(priv->gpio_headphone);
+ }
+
+ num_routes = acp3x_es83xx_configure_mics(priv);
+ if (num_routes > 0) {
+ ret = snd_soc_dapm_add_routes(&card->dapm, priv->mic_map, num_routes);
+ if (ret != 0)
+ device_remove_software_node(priv->codec_dev);
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_ops acp3x_es83xx_ops = {
+ .startup = acp3x_es83xx_codec_startup,
+};
+
+
+SND_SOC_DAILINK_DEF(codec,
+ DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8336:00", "ES8316 HiFi")));
+
+static const struct dmi_system_id acp3x_es83xx_dmi_table[] = {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXXW"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+ },
+ .driver_data = (void *)(ES83XX_ENABLE_DMIC),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "KLVL-WXX9"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+ },
+ .driver_data = (void *)(ES83XX_ENABLE_DMIC),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "BOM-WXX9"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1010"),
+ },
+ .driver_data = (void *)(ES83XX_ENABLE_DMIC|ES83XX_48_MHZ_MCLK),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1020"),
+ },
+ .driver_data = (void *)(ES83XX_ENABLE_DMIC),
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1040"),
+ },
+ .driver_data = (void *)(ES83XX_ENABLE_DMIC),
+ },
+ {}
+};
+
+static int acp3x_es83xx_configure_link(struct snd_soc_card *card, struct snd_soc_dai_link *link)
+{
+ link->codecs = codec;
+ link->num_codecs = ARRAY_SIZE(codec);
+ link->init = acp3x_es83xx_init;
+ link->ops = &acp3x_es83xx_ops;
+ link->dai_fmt = SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP;
+
+ return 0;
+}
+
+static int acp3x_es83xx_probe(struct snd_soc_card *card)
+{
+ int ret = 0;
+ struct device *dev = card->dev;
+ const struct dmi_system_id *dmi_id;
+
+ dmi_id = dmi_first_match(acp3x_es83xx_dmi_table);
+ if (dmi_id && dmi_id->driver_data) {
+ struct acp3x_es83xx_private *priv;
+ struct acp_card_drvdata *acp_drvdata;
+ struct acpi_device *adev;
+ struct device *codec_dev;
+
+ acp_drvdata = (struct acp_card_drvdata *)card->drvdata;
+
+ dev_info(dev, "matched DMI table with this system, trying to register sound card\n");
+
+ adev = acpi_dev_get_first_match_dev(acp_drvdata->acpi_mach->id, NULL, -1);
+ if (!adev) {
+ dev_err(dev, "Error cannot find '%s' dev\n", acp_drvdata->acpi_mach->id);
+ return -ENXIO;
+ }
+
+ codec_dev = acpi_get_first_physical_node(adev);
+ acpi_dev_put(adev);
+ if (!codec_dev) {
+ dev_warn(dev, "Error cannot find codec device, will defer probe\n");
+ return -EPROBE_DEFER;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ put_device(codec_dev);
+ return -ENOMEM;
+ }
+
+ priv->codec_dev = codec_dev;
+ priv->quirk = (unsigned long)dmi_id->driver_data;
+ acp_drvdata->mach_priv = priv;
+ dev_info(dev, "successfully probed the sound card\n");
+ } else {
+ ret = -ENODEV;
+ dev_warn(dev, "this system has a ES83xx codec defined in ACPI, but the driver doesn't have this system registered in DMI table\n");
+ }
+ return ret;
+}
+
+
+void acp3x_es83xx_init_ops(struct acp_mach_ops *ops)
+{
+ ops->probe = acp3x_es83xx_probe;
+ ops->configure_widgets = acp3x_es83xx_configure_widgets;
+ ops->configure_link = acp3x_es83xx_configure_link;
+ ops->suspend_pre = acp3x_es83xx_suspend_pre;
+ ops->resume_post = acp3x_es83xx_resume_post;
+}
diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.h b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.h
new file mode 100644
index 000000000000..03551ffdd9da
--- /dev/null
+++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2023 Marian Postevca <posteuca@mutex.one>
+ */
+
+#ifndef __ACP3X_ES83XX_H
+#define __ACP3X_ES83XX_H
+
+void acp3x_es83xx_init_ops(struct acp_mach_ops *ops);
+
+#endif
+
diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c
index 2a8984c1fa9c..8a40c6b3f4d8 100644
--- a/sound/soc/codecs/ak4642.c
+++ b/sound/soc/codecs/ak4642.c
@@ -628,37 +628,23 @@ static struct clk *ak4642_of_parse_mcko(struct device *dev)
#define ak4642_of_parse_mcko(d) 0
#endif
-static const struct of_device_id ak4642_of_match[];
-static const struct i2c_device_id ak4642_i2c_id[];
static int ak4642_i2c_probe(struct i2c_client *i2c)
{
struct device *dev = &i2c->dev;
- struct device_node *np = dev->of_node;
- const struct ak4642_drvdata *drvdata = NULL;
+ const struct ak4642_drvdata *drvdata;
struct regmap *regmap;
struct ak4642_priv *priv;
struct clk *mcko = NULL;
- if (np) {
- const struct of_device_id *of_id;
-
+ if (dev_fwnode(dev)) {
mcko = ak4642_of_parse_mcko(dev);
if (IS_ERR(mcko))
mcko = NULL;
-
- of_id = of_match_device(ak4642_of_match, dev);
- if (of_id)
- drvdata = of_id->data;
- } else {
- const struct i2c_device_id *id =
- i2c_match_id(ak4642_i2c_id, i2c);
- drvdata = (const struct ak4642_drvdata *)id->driver_data;
}
- if (!drvdata) {
- dev_err(dev, "Unknown device type\n");
- return -EINVAL;
- }
+ drvdata = i2c_get_match_data(i2c);
+ if (!drvdata)
+ return dev_err_probe(dev, -EINVAL, "Unknown device type\n");
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -681,7 +667,7 @@ static const struct of_device_id ak4642_of_match[] = {
{ .compatible = "asahi-kasei,ak4642", .data = &ak4642_drvdata},
{ .compatible = "asahi-kasei,ak4643", .data = &ak4643_drvdata},
{ .compatible = "asahi-kasei,ak4648", .data = &ak4648_drvdata},
- {},
+ {}
};
MODULE_DEVICE_TABLE(of, ak4642_of_match);
@@ -689,7 +675,7 @@ static const struct i2c_device_id ak4642_i2c_id[] = {
{ "ak4642", (kernel_ulong_t)&ak4642_drvdata },
{ "ak4643", (kernel_ulong_t)&ak4643_drvdata },
{ "ak4648", (kernel_ulong_t)&ak4648_drvdata },
- { }
+ {}
};
MODULE_DEVICE_TABLE(i2c, ak4642_i2c_id);
diff --git a/sound/soc/codecs/cs35l41-i2c.c b/sound/soc/codecs/cs35l41-i2c.c
index 7ea890d7d387..96414ee35285 100644
--- a/sound/soc/codecs/cs35l41-i2c.c
+++ b/sound/soc/codecs/cs35l41-i2c.c
@@ -35,7 +35,6 @@ static int cs35l41_i2c_probe(struct i2c_client *client)
struct device *dev = &client->dev;
struct cs35l41_hw_cfg *hw_cfg = dev_get_platdata(dev);
const struct regmap_config *regmap_config = &cs35l41_regmap_i2c;
- int ret;
cs35l41 = devm_kzalloc(dev, sizeof(struct cs35l41_private), GFP_KERNEL);
@@ -47,11 +46,9 @@ static int cs35l41_i2c_probe(struct i2c_client *client)
i2c_set_clientdata(client, cs35l41);
cs35l41->regmap = devm_regmap_init_i2c(client, regmap_config);
- if (IS_ERR(cs35l41->regmap)) {
- ret = PTR_ERR(cs35l41->regmap);
- dev_err(cs35l41->dev, "Failed to allocate register map: %d\n", ret);
- return ret;
- }
+ if (IS_ERR(cs35l41->regmap))
+ return dev_err_probe(cs35l41->dev, PTR_ERR(cs35l41->regmap),
+ "Failed to allocate register map\n");
return cs35l41_probe(cs35l41, hw_cfg);
}
@@ -83,7 +80,7 @@ MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match);
static struct i2c_driver cs35l41_i2c_driver = {
.driver = {
.name = "cs35l41",
- .pm = &cs35l41_pm_ops,
+ .pm = pm_ptr(&cs35l41_pm_ops),
.of_match_table = of_match_ptr(cs35l41_of_match),
.acpi_match_table = ACPI_PTR(cs35l41_acpi_match),
},
diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c
index 4ec306cd2f47..2ec5fdc875b1 100644
--- a/sound/soc/codecs/cs35l41-lib.c
+++ b/sound/soc/codecs/cs35l41-lib.c
@@ -1192,8 +1192,28 @@ bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type)
}
EXPORT_SYMBOL_GPL(cs35l41_safe_reset);
+/*
+ * Enabling the CS35L41_SHD_BOOST_ACTV and CS35L41_SHD_BOOST_PASS shared boosts
+ * does also require a call to cs35l41_mdsync_up(), but not before getting the
+ * PLL Lock signal.
+ *
+ * PLL Lock seems to be triggered soon after snd_pcm_start() is executed and
+ * SNDRV_PCM_TRIGGER_START command is processed, which happens (long) after the
+ * SND_SOC_DAPM_PRE_PMU event handler is invoked as part of snd_pcm_prepare().
+ *
+ * This event handler is where cs35l41_global_enable() is normally called from,
+ * but waiting for PLL Lock here will time out. Increasing the wait duration
+ * will not help, as the only consequence of it would be to add an unnecessary
+ * delay in the invocation of snd_pcm_start().
+ *
+ * Trying to move the wait in the SNDRV_PCM_TRIGGER_START callback is not a
+ * solution either, as the trigger is executed in an IRQ-off atomic context.
+ *
+ * The current approach is to invoke cs35l41_mdsync_up() right after receiving
+ * the PLL Lock interrupt, in the IRQ handler.
+ */
int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type,
- int enable, struct completion *pll_lock, bool firmware_running)
+ int enable, bool firmware_running)
{
int ret;
unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status, pup_pdn_mask;
@@ -1203,11 +1223,6 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
{CS35L41_GPIO_PAD_CONTROL, 0},
{CS35L41_PWR_CTRL1, 0, 3000},
};
- struct reg_sequence cs35l41_mdsync_up_seq[] = {
- {CS35L41_PWR_CTRL3, 0},
- {CS35L41_PWR_CTRL1, 0x00000000, 3000},
- {CS35L41_PWR_CTRL1, 0x00000001, 3000},
- };
pup_pdn_mask = enable ? CS35L41_PUP_DONE_MASK : CS35L41_PDN_DONE_MASK;
@@ -1241,24 +1256,12 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
cs35l41_mdsync_down_seq[0].def = pwr_ctrl3;
cs35l41_mdsync_down_seq[1].def = pad_control;
cs35l41_mdsync_down_seq[2].def = pwr_ctrl1;
+
ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_down_seq,
ARRAY_SIZE(cs35l41_mdsync_down_seq));
- if (!enable)
- break;
-
- if (!pll_lock)
- return -EINVAL;
-
- ret = wait_for_completion_timeout(pll_lock, msecs_to_jiffies(1000));
- if (ret == 0) {
- ret = -ETIMEDOUT;
- } else {
- regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3);
- pwr_ctrl3 |= CS35L41_SYNC_EN_MASK;
- cs35l41_mdsync_up_seq[0].def = pwr_ctrl3;
- ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq,
- ARRAY_SIZE(cs35l41_mdsync_up_seq));
- }
+ /* Activation to be completed later via cs35l41_mdsync_up() */
+ if (ret || enable)
+ return ret;
ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1,
int_status, int_status & pup_pdn_mask,
@@ -1266,7 +1269,7 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
if (ret)
dev_err(dev, "Enable(%d) failed: %d\n", enable, ret);
- // Clear PUP/PDN status
+ /* Clear PUP/PDN status */
regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask);
break;
case CS35L41_INT_BOOST:
@@ -1348,6 +1351,17 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4
}
EXPORT_SYMBOL_GPL(cs35l41_global_enable);
+/*
+ * To be called after receiving the IRQ Lock interrupt, in order to complete
+ * any shared boost activation initiated by cs35l41_global_enable().
+ */
+int cs35l41_mdsync_up(struct regmap *regmap)
+{
+ return regmap_update_bits(regmap, CS35L41_PWR_CTRL3,
+ CS35L41_SYNC_EN_MASK, CS35L41_SYNC_EN_MASK);
+}
+EXPORT_SYMBOL_GPL(cs35l41_mdsync_up);
+
int cs35l41_gpio_config(struct regmap *regmap, struct cs35l41_hw_cfg *hw_cfg)
{
struct cs35l41_gpio_cfg *gpio1 = &hw_cfg->gpio1;
diff --git a/sound/soc/codecs/cs35l41-spi.c b/sound/soc/codecs/cs35l41-spi.c
index 5c8bb24909eb..a6db44520c06 100644
--- a/sound/soc/codecs/cs35l41-spi.c
+++ b/sound/soc/codecs/cs35l41-spi.c
@@ -32,7 +32,6 @@ static int cs35l41_spi_probe(struct spi_device *spi)
const struct regmap_config *regmap_config = &cs35l41_regmap_spi;
struct cs35l41_hw_cfg *hw_cfg = dev_get_platdata(&spi->dev);
struct cs35l41_private *cs35l41;
- int ret;
cs35l41 = devm_kzalloc(&spi->dev, sizeof(struct cs35l41_private), GFP_KERNEL);
if (!cs35l41)
@@ -43,11 +42,9 @@ static int cs35l41_spi_probe(struct spi_device *spi)
spi_set_drvdata(spi, cs35l41);
cs35l41->regmap = devm_regmap_init_spi(spi, regmap_config);
- if (IS_ERR(cs35l41->regmap)) {
- ret = PTR_ERR(cs35l41->regmap);
- dev_err(&spi->dev, "Failed to allocate register map: %d\n", ret);
- return ret;
- }
+ if (IS_ERR(cs35l41->regmap))
+ return dev_err_probe(cs35l41->dev, PTR_ERR(cs35l41->regmap),
+ "Failed to allocate register map\n");
cs35l41->dev = &spi->dev;
cs35l41->irq = spi->irq;
@@ -83,7 +80,7 @@ MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match);
static struct spi_driver cs35l41_spi_driver = {
.driver = {
.name = "cs35l41",
- .pm = &cs35l41_pm_ops,
+ .pm = pm_ptr(&cs35l41_pm_ops),
.of_match_table = of_match_ptr(cs35l41_of_match),
.acpi_match_table = ACPI_PTR(cs35l41_acpi_match),
},
diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c
index 722b69a6de26..4bc64ba71cd6 100644
--- a/sound/soc/codecs/cs35l41.c
+++ b/sound/soc/codecs/cs35l41.c
@@ -386,10 +386,18 @@ static irqreturn_t cs35l41_irq(int irq, void *data)
struct cs35l41_private *cs35l41 = data;
unsigned int status[4] = { 0, 0, 0, 0 };
unsigned int masks[4] = { 0, 0, 0, 0 };
- int ret = IRQ_NONE;
unsigned int i;
+ int ret;
- pm_runtime_get_sync(cs35l41->dev);
+ ret = pm_runtime_resume_and_get(cs35l41->dev);
+ if (ret < 0) {
+ dev_err(cs35l41->dev,
+ "pm_runtime_resume_and_get failed in %s: %d\n",
+ __func__, ret);
+ return IRQ_NONE;
+ }
+
+ ret = IRQ_NONE;
for (i = 0; i < ARRAY_SIZE(status); i++) {
regmap_read(cs35l41->regmap,
@@ -459,7 +467,19 @@ static irqreturn_t cs35l41_irq(int irq, void *data)
if (status[2] & CS35L41_PLL_LOCK) {
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS3, CS35L41_PLL_LOCK);
- complete(&cs35l41->pll_lock);
+
+ if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV ||
+ cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS) {
+ ret = cs35l41_mdsync_up(cs35l41->regmap);
+ if (ret)
+ dev_err(cs35l41->dev, "MDSYNC-up failed: %d\n", ret);
+ else
+ dev_dbg(cs35l41->dev, "MDSYNC-up done\n");
+
+ dev_dbg(cs35l41->dev, "PUP-done status: %d\n",
+ !!(status[0] & CS35L41_PUP_DONE_MASK));
+ }
+
ret = IRQ_HANDLED;
}
@@ -500,11 +520,11 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w,
ARRAY_SIZE(cs35l41_pup_patch));
ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type,
- 1, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running);
+ 1, cs35l41->dsp.cs_dsp.running);
break;
case SND_SOC_DAPM_POST_PMD:
ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type,
- 0, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running);
+ 0, cs35l41->dsp.cs_dsp.running);
regmap_multi_reg_write_bypassed(cs35l41->regmap,
cs35l41_pdn_patch,
@@ -802,10 +822,6 @@ static const struct snd_pcm_hw_constraint_list cs35l41_constraints = {
static int cs35l41_pcm_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component);
-
- reinit_completion(&cs35l41->pll_lock);
-
if (substream->runtime)
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
@@ -1174,16 +1190,14 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
ret = devm_regulator_bulk_get(cs35l41->dev, CS35L41_NUM_SUPPLIES,
cs35l41->supplies);
- if (ret != 0) {
- dev_err(cs35l41->dev, "Failed to request core supplies: %d\n", ret);
- return ret;
- }
+ if (ret != 0)
+ return dev_err_probe(cs35l41->dev, ret,
+ "Failed to request core supplies\n");
ret = regulator_bulk_enable(CS35L41_NUM_SUPPLIES, cs35l41->supplies);
- if (ret != 0) {
- dev_err(cs35l41->dev, "Failed to enable core supplies: %d\n", ret);
- return ret;
- }
+ if (ret != 0)
+ return dev_err_probe(cs35l41->dev, ret,
+ "Failed to enable core supplies\n");
/* returning NULL can be an option if in stereo mode */
cs35l41->reset_gpio = devm_gpiod_get_optional(cs35l41->dev, "reset",
@@ -1195,8 +1209,8 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
dev_info(cs35l41->dev,
"Reset line busy, assuming shared reset\n");
} else {
- dev_err(cs35l41->dev,
- "Failed to get reset GPIO: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret,
+ "Failed to get reset GPIO\n");
goto err;
}
}
@@ -1212,8 +1226,8 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
int_status, int_status & CS35L41_OTP_BOOT_DONE,
1000, 100000);
if (ret) {
- dev_err(cs35l41->dev,
- "Failed waiting for OTP_BOOT_DONE: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret,
+ "Failed waiting for OTP_BOOT_DONE\n");
goto err;
}
@@ -1226,13 +1240,13 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, &regid);
if (ret < 0) {
- dev_err(cs35l41->dev, "Get Device ID failed: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "Get Device ID failed\n");
goto err;
}
ret = regmap_read(cs35l41->regmap, CS35L41_REVID, &reg_revid);
if (ret < 0) {
- dev_err(cs35l41->dev, "Get Revision ID failed: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "Get Revision ID failed\n");
goto err;
}
@@ -1257,7 +1271,7 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap);
if (ret < 0) {
- dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "OTP Unpack failed\n");
goto err;
}
@@ -1277,13 +1291,13 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
IRQF_ONESHOT | IRQF_SHARED | irq_pol,
"cs35l41", cs35l41);
if (ret != 0) {
- dev_err(cs35l41->dev, "Failed to request IRQ: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "Failed to request IRQ\n");
goto err;
}
ret = cs35l41_set_pdata(cs35l41);
if (ret < 0) {
- dev_err(cs35l41->dev, "Set pdata failed: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "Set pdata failed\n");
goto err;
}
@@ -1295,8 +1309,6 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
if (ret < 0)
goto err;
- init_completion(&cs35l41->pll_lock);
-
pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000);
pm_runtime_use_autosuspend(cs35l41->dev);
pm_runtime_mark_last_busy(cs35l41->dev);
@@ -1308,7 +1320,7 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
&soc_component_dev_cs35l41,
cs35l41_dai, ARRAY_SIZE(cs35l41_dai));
if (ret < 0) {
- dev_err(cs35l41->dev, "Register codec failed: %d\n", ret);
+ dev_err_probe(cs35l41->dev, ret, "Register codec failed\n");
goto err_pm;
}
@@ -1320,6 +1332,7 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *
return 0;
err_pm:
+ pm_runtime_dont_use_autosuspend(cs35l41->dev);
pm_runtime_disable(cs35l41->dev);
pm_runtime_put_noidle(cs35l41->dev);
@@ -1336,6 +1349,7 @@ EXPORT_SYMBOL_GPL(cs35l41_probe);
void cs35l41_remove(struct cs35l41_private *cs35l41)
{
pm_runtime_get_sync(cs35l41->dev);
+ pm_runtime_dont_use_autosuspend(cs35l41->dev);
pm_runtime_disable(cs35l41->dev);
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF);
@@ -1354,7 +1368,7 @@ void cs35l41_remove(struct cs35l41_private *cs35l41)
}
EXPORT_SYMBOL_GPL(cs35l41_remove);
-static int __maybe_unused cs35l41_runtime_suspend(struct device *dev)
+static int cs35l41_runtime_suspend(struct device *dev)
{
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
@@ -1371,7 +1385,7 @@ static int __maybe_unused cs35l41_runtime_suspend(struct device *dev)
return 0;
}
-static int __maybe_unused cs35l41_runtime_resume(struct device *dev)
+static int cs35l41_runtime_resume(struct device *dev)
{
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
int ret;
@@ -1400,7 +1414,7 @@ static int __maybe_unused cs35l41_runtime_resume(struct device *dev)
return 0;
}
-static int __maybe_unused cs35l41_sys_suspend(struct device *dev)
+static int cs35l41_sys_suspend(struct device *dev)
{
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
@@ -1410,7 +1424,7 @@ static int __maybe_unused cs35l41_sys_suspend(struct device *dev)
return 0;
}
-static int __maybe_unused cs35l41_sys_suspend_noirq(struct device *dev)
+static int cs35l41_sys_suspend_noirq(struct device *dev)
{
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
@@ -1420,7 +1434,7 @@ static int __maybe_unused cs35l41_sys_suspend_noirq(struct device *dev)
return 0;
}
-static int __maybe_unused cs35l41_sys_resume_noirq(struct device *dev)
+static int cs35l41_sys_resume_noirq(struct device *dev)
{
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
@@ -1430,7 +1444,7 @@ static int __maybe_unused cs35l41_sys_resume_noirq(struct device *dev)
return 0;
}
-static int __maybe_unused cs35l41_sys_resume(struct device *dev)
+static int cs35l41_sys_resume(struct device *dev)
{
struct cs35l41_private *cs35l41 = dev_get_drvdata(dev);
@@ -1440,13 +1454,12 @@ static int __maybe_unused cs35l41_sys_resume(struct device *dev)
return 0;
}
-const struct dev_pm_ops cs35l41_pm_ops = {
- SET_RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL)
+EXPORT_GPL_DEV_PM_OPS(cs35l41_pm_ops) = {
+ RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend, cs35l41_sys_resume)
- SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend_noirq, cs35l41_sys_resume_noirq)
+ SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend, cs35l41_sys_resume)
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend_noirq, cs35l41_sys_resume_noirq)
};
-EXPORT_SYMBOL_GPL(cs35l41_pm_ops);
MODULE_DESCRIPTION("ASoC CS35L41 driver");
MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, <david.rhodes@cirrus.com>");
diff --git a/sound/soc/codecs/cs35l41.h b/sound/soc/codecs/cs35l41.h
index 34d967d4372b..c85cbc1dd333 100644
--- a/sound/soc/codecs/cs35l41.h
+++ b/sound/soc/codecs/cs35l41.h
@@ -33,7 +33,6 @@ struct cs35l41_private {
int irq;
/* GPIO for /RST */
struct gpio_desc *reset_gpio;
- struct completion pll_lock;
};
int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg);
diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c
index 621af1785979..e1cebb9e4dc6 100644
--- a/sound/soc/codecs/cs35l45-tables.c
+++ b/sound/soc/codecs/cs35l45-tables.c
@@ -91,6 +91,7 @@ static const struct reg_default cs35l45_defaults[] = {
{ CS35L45_DSP1RX7_INPUT, 0x0000003A },
{ CS35L45_DSP1RX8_INPUT, 0x00000028 },
{ CS35L45_AMP_PCM_CONTROL, 0x00100000 },
+ { CS35L45_AMP_GAIN, 0x00002300 },
{ CS35L45_IRQ1_CFG, 0x00000000 },
{ CS35L45_IRQ1_MASK_1, 0xBFEFFFBF },
{ CS35L45_IRQ1_MASK_2, 0xFFFFFFFF },
@@ -156,7 +157,9 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg)
case CS35L45_DSP1RX6_INPUT:
case CS35L45_DSP1RX7_INPUT:
case CS35L45_DSP1RX8_INPUT:
+ case CS35L45_HVLV_CONFIG:
case CS35L45_AMP_PCM_CONTROL:
+ case CS35L45_AMP_GAIN:
case CS35L45_AMP_PCM_HPF_TST:
case CS35L45_IRQ1_CFG:
case CS35L45_IRQ1_STATUS:
diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c
index be4f4229576c..b68853e42fd1 100644
--- a/sound/soc/codecs/cs35l45.c
+++ b/sound/soc/codecs/cs35l45.c
@@ -169,6 +169,142 @@ static int cs35l45_dsp_audio_ev(struct snd_soc_dapm_widget *w,
return 0;
}
+static int cs35l45_activate_ctl(struct snd_soc_component *component,
+ const char *ctl_name, bool active)
+{
+ struct snd_card *card = component->card->snd_card;
+ struct snd_kcontrol *kcontrol;
+ struct snd_kcontrol_volatile *vd;
+ unsigned int index_offset;
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+
+ if (component->name_prefix)
+ snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s",
+ component->name_prefix, ctl_name);
+ else
+ snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s", ctl_name);
+
+ kcontrol = snd_soc_card_get_kcontrol(component->card, name);
+ if (!kcontrol) {
+ dev_err(component->dev, "Can't find kcontrol %s\n", name);
+ return -EINVAL;
+ }
+
+ index_offset = snd_ctl_get_ioff(kcontrol, &kcontrol->id);
+ vd = &kcontrol->vd[index_offset];
+ if (active)
+ vd->access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
+ else
+ vd->access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE;
+
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &kcontrol->id);
+
+ return 0;
+}
+
+static int cs35l45_amplifier_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct cs35l45_private *cs35l45 =
+ snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = cs35l45->amplifier_mode;
+
+ return 0;
+}
+
+static int cs35l45_amplifier_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct cs35l45_private *cs35l45 =
+ snd_soc_component_get_drvdata(component);
+ struct snd_soc_dapm_context *dapm =
+ snd_soc_component_get_dapm(component);
+ unsigned int amp_state;
+ int ret;
+
+ if ((ucontrol->value.integer.value[0] == cs35l45->amplifier_mode) ||
+ (ucontrol->value.integer.value[0] > AMP_MODE_RCV))
+ return 0;
+
+ snd_soc_dapm_mutex_lock(dapm);
+
+ ret = regmap_read(cs35l45->regmap, CS35L45_BLOCK_ENABLES, &amp_state);
+ if (ret < 0) {
+ dev_err(cs35l45->dev, "Failed to read AMP state: %d\n", ret);
+ snd_soc_dapm_mutex_unlock(dapm);
+ return ret;
+ }
+
+ regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
+ CS35L45_AMP_EN_MASK);
+ snd_soc_component_disable_pin_unlocked(component, "SPK");
+ snd_soc_dapm_sync_unlocked(dapm);
+
+ if (ucontrol->value.integer.value[0] == AMP_MODE_SPK) {
+ regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
+ CS35L45_RCV_EN_MASK);
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
+ CS35L45_BST_EN_MASK,
+ CS35L45_BST_ENABLE << CS35L45_BST_EN_SHIFT);
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_HVLV_CONFIG,
+ CS35L45_HVLV_MODE_MASK,
+ CS35L45_HVLV_OPERATION <<
+ CS35L45_HVLV_MODE_SHIFT);
+
+ ret = cs35l45_activate_ctl(component, "Analog PCM Volume", true);
+ if (ret < 0)
+ dev_err(cs35l45->dev,
+ "Unable to deactivate ctl (%d)\n", ret);
+
+ } else /* AMP_MODE_RCV */ {
+ regmap_set_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
+ CS35L45_RCV_EN_MASK);
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
+ CS35L45_BST_EN_MASK,
+ CS35L45_BST_DISABLE_FET_OFF <<
+ CS35L45_BST_EN_SHIFT);
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_HVLV_CONFIG,
+ CS35L45_HVLV_MODE_MASK,
+ CS35L45_FORCE_LV_OPERATION <<
+ CS35L45_HVLV_MODE_SHIFT);
+
+ regmap_clear_bits(cs35l45->regmap,
+ CS35L45_BLOCK_ENABLES2,
+ CS35L45_AMP_DRE_EN_MASK);
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_AMP_GAIN,
+ CS35L45_AMP_GAIN_PCM_MASK,
+ CS35L45_AMP_GAIN_PCM_13DBV <<
+ CS35L45_AMP_GAIN_PCM_SHIFT);
+
+ ret = cs35l45_activate_ctl(component, "Analog PCM Volume", false);
+ if (ret < 0)
+ dev_err(cs35l45->dev,
+ "Unable to deactivate ctl (%d)\n", ret);
+ }
+
+ if (amp_state & CS35L45_AMP_EN_MASK)
+ regmap_set_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
+ CS35L45_AMP_EN_MASK);
+
+ snd_soc_component_enable_pin_unlocked(component, "SPK");
+ snd_soc_dapm_sync_unlocked(dapm);
+ snd_soc_dapm_mutex_unlock(dapm);
+
+ cs35l45->amplifier_mode = ucontrol->value.integer.value[0];
+
+ return 1;
+}
+
static const char * const cs35l45_asp_tx_txt[] = {
"Zero", "ASP_RX1", "ASP_RX2",
"VMON", "IMON", "ERR_VOL",
@@ -281,6 +417,8 @@ static const struct snd_kcontrol_new cs35l45_dsp_muxes[] = {
static const struct snd_kcontrol_new cs35l45_dac_muxes[] = {
SOC_DAPM_ENUM("DACPCM Source", cs35l45_dacpcm_enums[0]),
};
+static const struct snd_kcontrol_new amp_en_ctl =
+ SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0);
static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = {
SND_SOC_DAPM_SPK("DSP1 Preload", NULL),
@@ -297,17 +435,25 @@ static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = {
SND_SOC_DAPM_SIGGEN("VMON_SRC"),
SND_SOC_DAPM_SIGGEN("IMON_SRC"),
+ SND_SOC_DAPM_SIGGEN("TEMPMON_SRC"),
SND_SOC_DAPM_SIGGEN("VDD_BATTMON_SRC"),
SND_SOC_DAPM_SIGGEN("VDD_BSTMON_SRC"),
SND_SOC_DAPM_SIGGEN("ERR_VOL"),
SND_SOC_DAPM_SIGGEN("AMP_INTP"),
SND_SOC_DAPM_SIGGEN("IL_TARGET"),
- SND_SOC_DAPM_ADC("VMON", NULL, CS35L45_BLOCK_ENABLES, CS35L45_VMON_EN_SHIFT, 0),
- SND_SOC_DAPM_ADC("IMON", NULL, CS35L45_BLOCK_ENABLES, CS35L45_IMON_EN_SHIFT, 0),
- SND_SOC_DAPM_ADC("VDD_BATTMON", NULL, CS35L45_BLOCK_ENABLES,
- CS35L45_VDD_BATTMON_EN_SHIFT, 0),
- SND_SOC_DAPM_ADC("VDD_BSTMON", NULL, CS35L45_BLOCK_ENABLES,
- CS35L45_VDD_BSTMON_EN_SHIFT, 0),
+
+ SND_SOC_DAPM_SUPPLY("VMON_EN", CS35L45_BLOCK_ENABLES, CS35L45_VMON_EN_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("IMON_EN", CS35L45_BLOCK_ENABLES, CS35L45_IMON_EN_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("TEMPMON_EN", CS35L45_BLOCK_ENABLES, CS35L45_TEMPMON_EN_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("VDD_BATTMON_EN", CS35L45_BLOCK_ENABLES, CS35L45_VDD_BATTMON_EN_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("VDD_BSTMON_EN", CS35L45_BLOCK_ENABLES, CS35L45_VDD_BSTMON_EN_SHIFT, 0, NULL, 0),
+
+ SND_SOC_DAPM_ADC("VMON", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("IMON", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("TEMPMON", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("VDD_BATTMON", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_ADC("VDD_BSTMON", NULL, SND_SOC_NOPM, 0, 0),
+
SND_SOC_DAPM_AIF_IN("ASP_RX1", NULL, 0, CS35L45_ASP_ENABLES1, CS35L45_ASP_RX1_EN_SHIFT, 0),
SND_SOC_DAPM_AIF_IN("ASP_RX2", NULL, 1, CS35L45_ASP_ENABLES1, CS35L45_ASP_RX2_EN_SHIFT, 0),
@@ -335,6 +481,8 @@ static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = {
SND_SOC_DAPM_MUX("DACPCM Source", SND_SOC_NOPM, 0, 0, &cs35l45_dac_muxes[0]),
+ SND_SOC_DAPM_SWITCH("AMP Enable", SND_SOC_NOPM, 0, 0, &amp_en_ctl),
+
SND_SOC_DAPM_OUT_DRV("AMP", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("SPK"),
@@ -367,9 +515,16 @@ static const struct snd_soc_dapm_route cs35l45_dapm_routes[] = {
/* Feedback */
{ "VMON", NULL, "VMON_SRC" },
{ "IMON", NULL, "IMON_SRC" },
+ { "TEMPMON", NULL, "TEMPMON_SRC" },
{ "VDD_BATTMON", NULL, "VDD_BATTMON_SRC" },
{ "VDD_BSTMON", NULL, "VDD_BSTMON_SRC" },
+ { "VMON", NULL, "VMON_EN" },
+ { "IMON", NULL, "IMON_EN" },
+ { "TEMPMON", NULL, "TEMPMON_EN" },
+ { "VDD_BATTMON", NULL, "VDD_BATTMON_EN" },
+ { "VDD_BSTMON", NULL, "VDD_BSTMON_EN" },
+
{ "Capture", NULL, "ASP_TX1"},
{ "Capture", NULL, "ASP_TX2"},
{ "Capture", NULL, "ASP_TX3"},
@@ -424,17 +579,34 @@ static const struct snd_soc_dapm_route cs35l45_dapm_routes[] = {
{"DSP1", NULL, "DSP_RX7 Source"},
{"DSP1", NULL, "DSP_RX8 Source"},
+ {"DSP1", NULL, "VMON_EN"},
+ {"DSP1", NULL, "IMON_EN"},
+ {"DSP1", NULL, "VDD_BATTMON_EN"},
+ {"DSP1", NULL, "VDD_BSTMON_EN"},
+ {"DSP1", NULL, "TEMPMON_EN"},
+
{"DSP1 Preload", NULL, "DSP1 Preloader"},
{"DSP1", NULL, "DSP1 Preloader"},
CS35L45_DAC_MUX_ROUTE("DACPCM"),
- { "SPK", NULL, "AMP"},
+ { "AMP Enable", "Switch", "AMP" },
+ { "SPK", NULL, "AMP Enable"},
};
+static const char * const amplifier_mode_texts[] = {"SPK", "RCV"};
+static SOC_ENUM_SINGLE_DECL(amplifier_mode_enum, SND_SOC_NOPM, 0,
+ amplifier_mode_texts);
+static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 1000, 300, 0);
static const DECLARE_TLV_DB_SCALE(cs35l45_dig_pcm_vol_tlv, -10225, 25, true);
static const struct snd_kcontrol_new cs35l45_controls[] = {
+ SOC_ENUM_EXT("Amplifier Mode", amplifier_mode_enum,
+ cs35l45_amplifier_mode_get, cs35l45_amplifier_mode_put),
+ SOC_SINGLE_TLV("Analog PCM Volume", CS35L45_AMP_GAIN,
+ CS35L45_AMP_GAIN_PCM_SHIFT,
+ CS35L45_AMP_GAIN_PCM_MASK >> CS35L45_AMP_GAIN_PCM_SHIFT,
+ 0, amp_gain_tlv),
/* Ignore bit 0: it is beyond the resolution of TLV_DB_SCALE */
SOC_SINGLE_S_TLV("Digital PCM Volume",
CS35L45_AMP_PCM_CONTROL,
@@ -1023,7 +1195,10 @@ static irqreturn_t cs35l45_spk_safe_err(int irq, void *data)
i = irq - regmap_irq_get_virq(cs35l45->irq_data, 0);
- dev_err(cs35l45->dev, "%s condition detected!\n", cs35l45_irqs[i].name);
+ if (i < 0 || i >= ARRAY_SIZE(cs35l45_irqs))
+ dev_err(cs35l45->dev, "Unspecified global error condition (%d) detected!\n", irq);
+ else
+ dev_err(cs35l45->dev, "%s condition detected!\n", cs35l45_irqs[i].name);
return IRQ_HANDLED;
}
@@ -1101,6 +1276,8 @@ static int cs35l45_initialize(struct cs35l45_private *cs35l45)
if (ret < 0)
return ret;
+ cs35l45->amplifier_mode = AMP_MODE_SPK;
+
return 0;
}
diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h
index 61135a316df3..e2ebcf58d7e0 100644
--- a/sound/soc/codecs/cs35l45.h
+++ b/sound/soc/codecs/cs35l45.h
@@ -61,9 +61,11 @@
#define CS35L45_DSP1RX6_INPUT 0x00004C54
#define CS35L45_DSP1RX7_INPUT 0x00004C58
#define CS35L45_DSP1RX8_INPUT 0x00004C5C
+#define CS35L45_HVLV_CONFIG 0x00006400
#define CS35L45_LDPM_CONFIG 0x00006404
#define CS35L45_AMP_PCM_CONTROL 0x00007000
#define CS35L45_AMP_PCM_HPF_TST 0x00007004
+#define CS35L45_AMP_GAIN 0x00007800
#define CS35L45_IRQ1_CFG 0x0000E000
#define CS35L45_IRQ1_STATUS 0x0000E004
#define CS35L45_IRQ1_EINT_1 0x0000E010
@@ -163,16 +165,24 @@
/* BLOCK_ENABLES */
#define CS35L45_IMON_EN_SHIFT 13
#define CS35L45_VMON_EN_SHIFT 12
+#define CS35L45_TEMPMON_EN_SHIFT 10
#define CS35L45_VDD_BSTMON_EN_SHIFT 9
#define CS35L45_VDD_BATTMON_EN_SHIFT 8
#define CS35L45_BST_EN_SHIFT 4
#define CS35L45_BST_EN_MASK GENMASK(5, 4)
+#define CS35L45_RCV_EN_SHIFT 2
+#define CS35L45_RCV_EN_MASK BIT(2)
+#define CS35L45_AMP_EN_SHIFT 0
+#define CS35L45_AMP_EN_MASK BIT(0)
-#define CS35L45_BST_DISABLE_FET_ON 0x01
+#define CS35L45_BST_DISABLE_FET_OFF 0x00
+#define CS35L45_BST_DISABLE_FET_ON 0x01
+#define CS35L45_BST_ENABLE 0x02
/* BLOCK_ENABLES2 */
#define CS35L45_ASP_EN_SHIFT 27
-
+#define CS35L45_AMP_DRE_EN_SHIFT 20
+#define CS35L45_AMP_DRE_EN_MASK BIT(20)
#define CS35L45_MEM_RDY_SHIFT 1
#define CS35L45_MEM_RDY_MASK BIT(1)
@@ -266,6 +276,13 @@
#define CS35L45_ASP_WL_SHIFT 0
#define CS35L45_ASP_WL_MASK GENMASK(5, 0)
+/* HVLV_CONFIG */
+#define CS35L45_FORCE_LV_OPERATION 0x01
+#define CS35L45_FORCE_HV_OPERATION 0x02
+#define CS35L45_HVLV_OPERATION 0x03
+#define CS35L45_HVLV_MODE_SHIFT 0
+#define CS35L45_HVLV_MODE_MASK GENMASK(1, 0)
+
/* AMP_PCM_CONTROL */
#define CS35L45_AMP_VOL_PCM_SHIFT 0
#define CS35L45_AMP_VOL_PCM_WIDTH 11
@@ -275,6 +292,15 @@
#define CS35L45_HPF_44P1 0x000108BD
#define CS35L45_HPF_88P2 0x0001045F
+/* AMP_GAIN_PCM */
+#define CS35L45_AMP_GAIN_PCM_10DBV 0x00
+#define CS35L45_AMP_GAIN_PCM_13DBV 0x01
+#define CS35L45_AMP_GAIN_PCM_16DBV 0x02
+#define CS35L45_AMP_GAIN_PCM_19DBV 0x03
+
+#define CS35L45_AMP_GAIN_PCM_SHIFT 8
+#define CS35L45_AMP_GAIN_PCM_MASK GENMASK(9, 8)
+
/* IRQ1_EINT_4 */
#define CS35L45_OTP_BOOT_DONE_STS_MASK BIT(1)
#define CS35L45_OTP_BUSY_MASK BIT(0)
@@ -396,6 +422,11 @@ enum control_bus_type {
CONTROL_BUS_SPI = 1,
};
+enum amp_mode {
+ AMP_MODE_SPK = 0,
+ AMP_MODE_RCV = 1,
+};
+
#define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_3LE| \
SNDRV_PCM_FMTBIT_S24_LE)
@@ -464,6 +495,7 @@ struct cs35l45_private {
bool sysclk_set;
u8 slot_width;
u8 slot_count;
+ int amplifier_mode;
int irq_invert;
int irq;
unsigned int i2c_addr;
diff --git a/sound/soc/codecs/cs42xx8-i2c.c b/sound/soc/codecs/cs42xx8-i2c.c
index a422472820fb..9028c0f0fe77 100644
--- a/sound/soc/codecs/cs42xx8-i2c.c
+++ b/sound/soc/codecs/cs42xx8-i2c.c
@@ -18,21 +18,15 @@
#include "cs42xx8.h"
-static const struct of_device_id cs42xx8_of_match[];
-
static int cs42xx8_i2c_probe(struct i2c_client *i2c)
{
int ret;
struct cs42xx8_driver_data *drvdata;
- const struct of_device_id *of_id;
-
- of_id = of_match_device(cs42xx8_of_match, &i2c->dev);
- if (!of_id) {
- dev_err(&i2c->dev, "failed to find driver data\n");
- return -EINVAL;
- }
- drvdata = (struct cs42xx8_driver_data *)of_id->data;
+ drvdata = (struct cs42xx8_driver_data *)i2c_get_match_data(i2c);
+ if (!drvdata)
+ return dev_err_probe(&i2c->dev, -EINVAL,
+ "failed to find driver data\n");
ret = cs42xx8_probe(&i2c->dev,
devm_regmap_init_i2c(i2c, &cs42xx8_regmap_config), drvdata);
diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c
index a8f347f1affb..e53b2856d625 100644
--- a/sound/soc/codecs/es8316.c
+++ b/sound/soc/codecs/es8316.c
@@ -27,7 +27,6 @@
* MCLK/LRCK ratios, but we also add ratio 400, which is commonly used on
* Intel Cherry Trail platforms (19.2MHz MCLK, 48kHz LRCK).
*/
-#define NR_SUPPORTED_MCLK_LRCK_RATIOS ARRAY_SIZE(supported_mclk_lrck_ratios)
static const unsigned int supported_mclk_lrck_ratios[] = {
256, 384, 400, 500, 512, 768, 1024
};
@@ -40,7 +39,7 @@ struct es8316_priv {
struct snd_soc_jack *jack;
int irq;
unsigned int sysclk;
- unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS];
+ unsigned int allowed_rates[ARRAY_SIZE(supported_mclk_lrck_ratios)];
struct snd_pcm_hw_constraint_list sysclk_constraints;
bool jd_inverted;
};
@@ -382,7 +381,7 @@ static int es8316_set_dai_sysclk(struct snd_soc_dai *codec_dai,
/* Limit supported sample rates to ones that can be autodetected
* by the codec running in slave mode.
*/
- for (i = 0; i < NR_SUPPORTED_MCLK_LRCK_RATIOS; i++) {
+ for (i = 0; i < ARRAY_SIZE(supported_mclk_lrck_ratios); i++) {
const unsigned int ratio = supported_mclk_lrck_ratios[i];
if (freq % ratio == 0)
@@ -470,19 +469,42 @@ static int es8316_pcm_hw_params(struct snd_pcm_substream *substream,
u8 bclk_divider;
u16 lrck_divider;
int i;
+ unsigned int clk = es8316->sysclk / 2;
+ bool clk_valid = false;
+
+ /* We will start with halved sysclk and see if we can use it
+ * for proper clocking. This is to minimise the risk of running
+ * the CODEC with a too high frequency. We have an SKU where
+ * the sysclk frequency is 48Mhz and this causes the sound to be
+ * sped up. If we can run with a halved sysclk, we will use it,
+ * if we can't use it, then full sysclk will be used.
+ */
+ do {
+ /* Validate supported sample rates that are autodetected from MCLK */
+ for (i = 0; i < ARRAY_SIZE(supported_mclk_lrck_ratios); i++) {
+ const unsigned int ratio = supported_mclk_lrck_ratios[i];
+
+ if (clk % ratio != 0)
+ continue;
+ if (clk / ratio == params_rate(params))
+ break;
+ }
+ if (i == ARRAY_SIZE(supported_mclk_lrck_ratios)) {
+ if (clk == es8316->sysclk)
+ return -EINVAL;
+ clk = es8316->sysclk;
+ } else {
+ clk_valid = true;
+ }
+ } while (!clk_valid);
- /* Validate supported sample rates that are autodetected from MCLK */
- for (i = 0; i < NR_SUPPORTED_MCLK_LRCK_RATIOS; i++) {
- const unsigned int ratio = supported_mclk_lrck_ratios[i];
-
- if (es8316->sysclk % ratio != 0)
- continue;
- if (es8316->sysclk / ratio == params_rate(params))
- break;
+ if (clk != es8316->sysclk) {
+ snd_soc_component_update_bits(component, ES8316_CLKMGR_CLKSW,
+ ES8316_CLKMGR_CLKSW_MCLK_DIV,
+ ES8316_CLKMGR_CLKSW_MCLK_DIV);
}
- if (i == NR_SUPPORTED_MCLK_LRCK_RATIOS)
- return -EINVAL;
- lrck_divider = es8316->sysclk / params_rate(params);
+
+ lrck_divider = clk / params_rate(params);
bclk_divider = lrck_divider / 4;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
@@ -526,7 +548,7 @@ static int es8316_mute(struct snd_soc_dai *dai, int mute, int direction)
}
#define ES8316_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
- SNDRV_PCM_FMTBIT_S24_LE)
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops es8316_ops = {
.startup = es8316_pcm_startup,
diff --git a/sound/soc/codecs/es8316.h b/sound/soc/codecs/es8316.h
index c335138e2837..0ff16f948690 100644
--- a/sound/soc/codecs/es8316.h
+++ b/sound/soc/codecs/es8316.h
@@ -129,4 +129,7 @@
#define ES8316_GPIO_FLAG_GM_NOT_SHORTED 0x02
#define ES8316_GPIO_FLAG_HP_NOT_INSERTED 0x04
+/* ES8316_CLKMGR_CLKSW */
+#define ES8316_CLKMGR_CLKSW_MCLK_DIV 0x80
+
#endif
diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c
index 1756edb35961..a220342c3d77 100644
--- a/sound/soc/codecs/tas571x.c
+++ b/sound/soc/codecs/tas571x.c
@@ -829,14 +829,10 @@ static struct snd_soc_dai_driver tas571x_dai = {
.ops = &tas571x_dai_ops,
};
-static const struct of_device_id tas571x_of_match[] __maybe_unused;
-static const struct i2c_device_id tas571x_i2c_id[];
-
static int tas571x_i2c_probe(struct i2c_client *client)
{
struct tas571x_private *priv;
struct device *dev = &client->dev;
- const struct of_device_id *of_id;
int i, ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
@@ -844,14 +840,7 @@ static int tas571x_i2c_probe(struct i2c_client *client)
return -ENOMEM;
i2c_set_clientdata(client, priv);
- of_id = of_match_device(tas571x_of_match, dev);
- if (of_id)
- priv->chip = of_id->data;
- else {
- const struct i2c_device_id *id =
- i2c_match_id(tas571x_i2c_id, client);
- priv->chip = (void *) id->driver_data;
- }
+ priv->chip = i2c_get_match_data(client);
priv->mclk = devm_clk_get(dev, "mclk");
if (IS_ERR(priv->mclk) && PTR_ERR(priv->mclk) != -ENOENT) {
diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c
index 49b33a256cd7..b27b5ae1e4b2 100644
--- a/sound/soc/codecs/tlv320aic32x4-i2c.c
+++ b/sound/soc/codecs/tlv320aic32x4-i2c.c
@@ -16,33 +16,20 @@
#include "tlv320aic32x4.h"
-static const struct of_device_id aic32x4_of_id[];
-static const struct i2c_device_id aic32x4_i2c_id[];
-
static int aic32x4_i2c_probe(struct i2c_client *i2c)
{
struct regmap *regmap;
struct regmap_config config;
+ enum aic32x4_type type;
config = aic32x4_regmap_config;
config.reg_bits = 8;
config.val_bits = 8;
regmap = devm_regmap_init_i2c(i2c, &config);
+ type = (uintptr_t)i2c_get_match_data(i2c);
- if (i2c->dev.of_node) {
- const struct of_device_id *oid;
-
- oid = of_match_node(aic32x4_of_id, i2c->dev.of_node);
- dev_set_drvdata(&i2c->dev, (void *)oid->data);
- } else {
- const struct i2c_device_id *id;
-
- id = i2c_match_id(aic32x4_i2c_id, i2c);
- dev_set_drvdata(&i2c->dev, (void *)id->driver_data);
- }
-
- return aic32x4_probe(&i2c->dev, regmap);
+ return aic32x4_probe(&i2c->dev, regmap, type);
}
static void aic32x4_i2c_remove(struct i2c_client *i2c)
diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c
index 03cce8d6404f..d5976c91766e 100644
--- a/sound/soc/codecs/tlv320aic32x4-spi.c
+++ b/sound/soc/codecs/tlv320aic32x4-spi.c
@@ -16,12 +16,11 @@
#include "tlv320aic32x4.h"
-static const struct of_device_id aic32x4_of_id[];
-
static int aic32x4_spi_probe(struct spi_device *spi)
{
struct regmap *regmap;
struct regmap_config config;
+ enum aic32x4_type type;
config = aic32x4_regmap_config;
config.reg_bits = 7;
@@ -30,20 +29,9 @@ static int aic32x4_spi_probe(struct spi_device *spi)
config.read_flag_mask = 0x01;
regmap = devm_regmap_init_spi(spi, &config);
+ type = (uintptr_t)spi_get_device_match_data(spi);
- if (spi->dev.of_node) {
- const struct of_device_id *oid;
-
- oid = of_match_node(aic32x4_of_id, spi->dev.of_node);
- dev_set_drvdata(&spi->dev, (void *)oid->data);
- } else {
- const struct spi_device_id *id_entry;
-
- id_entry = spi_get_device_id(spi);
- dev_set_drvdata(&spi->dev, (void *)id_entry->driver_data);
- }
-
- return aic32x4_probe(&spi->dev, regmap);
+ return aic32x4_probe(&spi->dev, regmap, type);
}
static void aic32x4_spi_remove(struct spi_device *spi)
diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c
index 6829834a412f..5c0c81da06db 100644
--- a/sound/soc/codecs/tlv320aic32x4.c
+++ b/sound/soc/codecs/tlv320aic32x4.c
@@ -1333,7 +1333,8 @@ error_ldo:
return ret;
}
-int aic32x4_probe(struct device *dev, struct regmap *regmap)
+int aic32x4_probe(struct device *dev, struct regmap *regmap,
+ enum aic32x4_type type)
{
struct aic32x4_priv *aic32x4;
struct aic32x4_pdata *pdata = dev->platform_data;
@@ -1349,7 +1350,7 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap)
return -ENOMEM;
aic32x4->dev = dev;
- aic32x4->type = (uintptr_t)dev_get_drvdata(dev);
+ aic32x4->type = type;
dev_set_drvdata(dev, aic32x4);
diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h
index d6101ce73f80..f68a846ef61d 100644
--- a/sound/soc/codecs/tlv320aic32x4.h
+++ b/sound/soc/codecs/tlv320aic32x4.h
@@ -17,7 +17,8 @@ enum aic32x4_type {
};
extern const struct regmap_config aic32x4_regmap_config;
-int aic32x4_probe(struct device *dev, struct regmap *regmap);
+int aic32x4_probe(struct device *dev, struct regmap *regmap,
+ enum aic32x4_type type);
void aic32x4_remove(struct device *dev);
int aic32x4_register_clocks(struct device *dev, const char *mclk_name);
diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c
index 6d22f7d40ec2..28c0ba348634 100644
--- a/sound/soc/codecs/wm8580.c
+++ b/sound/soc/codecs/wm8580.c
@@ -988,16 +988,8 @@ static const struct wm8580_driver_data wm8581_data = {
.num_dacs = 4,
};
-static const struct of_device_id wm8580_of_match[] = {
- { .compatible = "wlf,wm8580", .data = &wm8580_data },
- { .compatible = "wlf,wm8581", .data = &wm8581_data },
- { },
-};
-MODULE_DEVICE_TABLE(of, wm8580_of_match);
-
static int wm8580_i2c_probe(struct i2c_client *i2c)
{
- const struct of_device_id *of_id;
struct wm8580_priv *wm8580;
int ret, i;
@@ -1022,14 +1014,9 @@ static int wm8580_i2c_probe(struct i2c_client *i2c)
i2c_set_clientdata(i2c, wm8580);
- of_id = of_match_device(wm8580_of_match, &i2c->dev);
- if (of_id)
- wm8580->drvdata = of_id->data;
-
- if (!wm8580->drvdata) {
- dev_err(&i2c->dev, "failed to find driver data\n");
- return -EINVAL;
- }
+ wm8580->drvdata = i2c_get_match_data(i2c);
+ if (!wm8580->drvdata)
+ return dev_err_probe(&i2c->dev, -EINVAL, "failed to find driver data\n");
ret = devm_snd_soc_register_component(&i2c->dev,
&soc_component_dev_wm8580, wm8580_dai, ARRAY_SIZE(wm8580_dai));
@@ -1037,6 +1024,13 @@ static int wm8580_i2c_probe(struct i2c_client *i2c)
return ret;
}
+static const struct of_device_id wm8580_of_match[] = {
+ { .compatible = "wlf,wm8580", .data = &wm8580_data },
+ { .compatible = "wlf,wm8581", .data = &wm8581_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, wm8580_of_match);
+
static const struct i2c_device_id wm8580_i2c_id[] = {
{ "wm8580", (kernel_ulong_t)&wm8580_data },
{ "wm8581", (kernel_ulong_t)&wm8581_data },
diff --git a/sound/soc/mediatek/common/mtk-dsp-sof-common.c b/sound/soc/mediatek/common/mtk-dsp-sof-common.c
index 6fef16306f74..f3894010f656 100644
--- a/sound/soc/mediatek/common/mtk-dsp-sof-common.c
+++ b/sound/soc/mediatek/common/mtk-dsp-sof-common.c
@@ -54,6 +54,8 @@ int mtk_sof_card_probe(struct snd_soc_card *card)
{
int i;
struct snd_soc_dai_link *dai_link;
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card);
+ struct mtk_sof_priv *sof_priv = soc_card_data->sof_priv;
/* Set stream_name to help sof bind widgets */
for_each_card_prelinks(card, i, dai_link) {
@@ -61,10 +63,81 @@ int mtk_sof_card_probe(struct snd_soc_card *card)
dai_link->stream_name = dai_link->name;
}
+ INIT_LIST_HEAD(&sof_priv->dai_link_list);
+
return 0;
}
EXPORT_SYMBOL_GPL(mtk_sof_card_probe);
+static struct snd_soc_pcm_runtime *mtk_sof_find_tplg_be(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card);
+ struct mtk_sof_priv *sof_priv = soc_card_data->sof_priv;
+ struct snd_soc_pcm_runtime *fe;
+ struct snd_soc_pcm_runtime *be;
+ struct snd_soc_dpcm *dpcm;
+ int i, stream;
+
+ for_each_pcm_streams(stream) {
+ fe = NULL;
+ for_each_dpcm_fe(rtd, stream, dpcm) {
+ fe = dpcm->fe;
+ if (fe)
+ break;
+ }
+
+ if (!fe)
+ continue;
+
+ for_each_dpcm_be(fe, stream, dpcm) {
+ be = dpcm->be;
+ if (be == rtd)
+ continue;
+
+ for (i = 0; i < sof_priv->num_streams; i++) {
+ const struct sof_conn_stream *conn = &sof_priv->conn_streams[i];
+
+ if (!strcmp(be->dai_link->name, conn->sof_link))
+ return be;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/* fixup the BE DAI link to match any values from topology */
+static int mtk_sof_check_tplg_be_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card);
+ struct mtk_sof_priv *sof_priv = soc_card_data->sof_priv;
+ struct snd_soc_pcm_runtime *sof_be;
+ struct mtk_dai_link *dai_link;
+ int ret = 0;
+
+ sof_be = mtk_sof_find_tplg_be(rtd);
+ if (sof_be) {
+ if (sof_priv->sof_dai_link_fixup)
+ ret = sof_priv->sof_dai_link_fixup(rtd, params);
+ else if (sof_be->dai_link->be_hw_params_fixup)
+ ret = sof_be->dai_link->be_hw_params_fixup(sof_be, params);
+ } else {
+ list_for_each_entry(dai_link, &sof_priv->dai_link_list, list) {
+ if (strcmp(dai_link->name, rtd->dai_link->name) == 0) {
+ if (dai_link->be_hw_params_fixup)
+ ret = dai_link->be_hw_params_fixup(rtd, params);
+
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
int mtk_sof_card_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
@@ -72,6 +145,8 @@ int mtk_sof_card_late_probe(struct snd_soc_card *card)
struct mtk_soc_card_data *soc_card_data =
snd_soc_card_get_drvdata(card);
struct mtk_sof_priv *sof_priv = soc_card_data->sof_priv;
+ struct snd_soc_dai_link *dai_link;
+ struct mtk_dai_link *mtk_dai_link;
int i;
/* 1. find sof component */
@@ -86,25 +161,37 @@ int mtk_sof_card_late_probe(struct snd_soc_card *card)
return 0;
}
- /* 2. add route path and fixup callback */
+ /* 2. overwrite all BE fixups, and backup the existing fixup */
+ for_each_card_prelinks(card, i, dai_link) {
+ if (dai_link->be_hw_params_fixup) {
+ mtk_dai_link = devm_kzalloc(card->dev,
+ sizeof(*mtk_dai_link),
+ GFP_KERNEL);
+ if (!mtk_dai_link)
+ return -ENOMEM;
+
+ mtk_dai_link->be_hw_params_fixup = dai_link->be_hw_params_fixup;
+ mtk_dai_link->name = dai_link->name;
+
+ list_add(&mtk_dai_link->list, &sof_priv->dai_link_list);
+ }
+
+ if (dai_link->no_pcm)
+ dai_link->be_hw_params_fixup = mtk_sof_check_tplg_be_dai_link_fixup;
+ }
+
+ /* 3. add route path and SOF_BE fixup callback */
for (i = 0; i < sof_priv->num_streams; i++) {
const struct sof_conn_stream *conn = &sof_priv->conn_streams[i];
struct snd_soc_pcm_runtime *sof_rtd = NULL;
- struct snd_soc_pcm_runtime *normal_rtd = NULL;
for_each_card_rtds(card, rtd) {
if (!strcmp(rtd->dai_link->name, conn->sof_link)) {
sof_rtd = rtd;
- continue;
- }
- if (!strcmp(rtd->dai_link->name, conn->normal_link)) {
- normal_rtd = rtd;
- continue;
- }
- if (normal_rtd && sof_rtd)
break;
+ }
}
- if (normal_rtd && sof_rtd) {
+ if (sof_rtd) {
int j;
struct snd_soc_dai *cpu_dai;
@@ -131,13 +218,9 @@ int mtk_sof_card_late_probe(struct snd_soc_card *card)
}
}
+ /* overwrite SOF BE fixup */
sof_rtd->dai_link->be_hw_params_fixup =
sof_comp->driver->be_hw_params_fixup;
- if (sof_priv->sof_dai_link_fixup)
- normal_rtd->dai_link->be_hw_params_fixup =
- sof_priv->sof_dai_link_fixup;
- else
- normal_rtd->dai_link->be_hw_params_fixup = mtk_sof_dai_link_fixup;
}
}
diff --git a/sound/soc/mediatek/common/mtk-dsp-sof-common.h b/sound/soc/mediatek/common/mtk-dsp-sof-common.h
index dd38c4a93574..4bc5e1c0c8ed 100644
--- a/sound/soc/mediatek/common/mtk-dsp-sof-common.h
+++ b/sound/soc/mediatek/common/mtk-dsp-sof-common.h
@@ -18,11 +18,19 @@ struct sof_conn_stream {
int stream_dir;
};
+struct mtk_dai_link {
+ const char *name;
+ int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params);
+ struct list_head list;
+};
+
struct mtk_sof_priv {
const struct sof_conn_stream *conn_streams;
int num_streams;
int (*sof_dai_link_fixup)(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params);
+ struct list_head dai_link_list;
};
int mtk_sof_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c
index 9017f48b6272..e2416b981e1f 100644
--- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c
+++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c
@@ -19,6 +19,8 @@
#include "../../codecs/mt6359.h"
#include "../common/mtk-afe-platform-driver.h"
#include "../common/mtk-soundcard-driver.h"
+#include "../common/mtk-dsp-sof-common.h"
+#include "../common/mtk-soc-card.h"
#define CKSYS_AUD_TOP_CFG 0x032c
#define RG_TEST_ON BIT(0)
@@ -45,6 +47,11 @@
*/
#define NAU8825_CODEC_DAI "nau8825-hifi"
+#define SOF_DMA_DL2 "SOF_DMA_DL2"
+#define SOF_DMA_DL3 "SOF_DMA_DL3"
+#define SOF_DMA_UL4 "SOF_DMA_UL4"
+#define SOF_DMA_UL5 "SOF_DMA_UL5"
+
/* FE */
SND_SOC_DAILINK_DEFS(playback2,
DAILINK_COMP_ARRAY(COMP_CPU("DL2")),
@@ -176,6 +183,49 @@ SND_SOC_DAILINK_DEFS(ul_src,
"dmic-hifi")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(AFE_SOF_DL2,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_DL2")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(AFE_SOF_DL3,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_DL3")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(AFE_SOF_UL4,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_UL4")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(AFE_SOF_UL5,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_UL5")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static const struct sof_conn_stream g_sof_conn_streams[] = {
+ {
+ .sof_link = "AFE_SOF_DL2",
+ .sof_dma = SOF_DMA_DL2,
+ .stream_dir = SNDRV_PCM_STREAM_PLAYBACK
+ },
+ {
+ .sof_link = "AFE_SOF_DL3",
+ .sof_dma = SOF_DMA_DL3,
+ .stream_dir = SNDRV_PCM_STREAM_PLAYBACK
+ },
+ {
+ .sof_link = "AFE_SOF_UL4",
+ .sof_dma = SOF_DMA_UL4,
+ .stream_dir = SNDRV_PCM_STREAM_CAPTURE
+ },
+ {
+ .sof_link = "AFE_SOF_UL5",
+ .sof_dma = SOF_DMA_UL5,
+ .stream_dir = SNDRV_PCM_STREAM_CAPTURE
+ },
+};
+
struct mt8188_mt6359_priv {
struct snd_soc_jack dp_jack;
struct snd_soc_jack hdmi_jack;
@@ -246,6 +296,15 @@ static const struct snd_soc_dapm_widget mt8188_mt6359_widgets[] = {
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SINK("HDMI"),
SND_SOC_DAPM_SINK("DP"),
+ SND_SOC_DAPM_MIXER(SOF_DMA_DL2, SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER(SOF_DMA_DL3, SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER(SOF_DMA_UL4, SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER(SOF_DMA_UL5, SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ /* dynamic pinctrl */
+ SND_SOC_DAPM_PINCTRL("ETDM_SPK_PIN", "aud_etdm_spk_on", "aud_etdm_spk_off"),
+ SND_SOC_DAPM_PINCTRL("ETDM_HP_PIN", "aud_etdm_hp_on", "aud_etdm_hp_off"),
+ SND_SOC_DAPM_PINCTRL("MTKAIF_PIN", "aud_mtkaif_on", "aud_mtkaif_off"),
};
static const struct snd_kcontrol_new mt8188_mt6359_controls[] = {
@@ -261,12 +320,26 @@ static const struct snd_kcontrol_new mt8188_nau8825_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
};
+static const struct snd_soc_dapm_route mt8188_mt6359_routes[] = {
+ /* SOF Uplink */
+ {SOF_DMA_UL4, NULL, "O034"},
+ {SOF_DMA_UL4, NULL, "O035"},
+ {SOF_DMA_UL5, NULL, "O036"},
+ {SOF_DMA_UL5, NULL, "O037"},
+ /* SOF Downlink */
+ {"I070", NULL, SOF_DMA_DL2},
+ {"I071", NULL, SOF_DMA_DL2},
+ {"I020", NULL, SOF_DMA_DL3},
+ {"I021", NULL, SOF_DMA_DL3},
+};
+
static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_component *cmpnt_afe =
snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME);
struct snd_soc_component *cmpnt_codec =
asoc_rtd_to_codec(rtd, 0)->component;
+ struct snd_soc_dapm_widget *pin_w = NULL, *w;
struct mtk_base_afe *afe;
struct mt8188_afe_private *afe_priv;
struct mtkaif_param *param;
@@ -306,6 +379,18 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd)
return 0;
}
+ for_each_card_widgets(rtd->card, w) {
+ if (!strcmp(w->name, "MTKAIF_PIN")) {
+ pin_w = w;
+ break;
+ }
+ }
+
+ if (pin_w)
+ dapm_pinctrl_event(pin_w, NULL, SND_SOC_DAPM_PRE_PMU);
+ else
+ dev_dbg(afe->dev, "%s(), no pinmux widget, please check if default on\n", __func__);
+
pm_runtime_get_sync(afe->dev);
mt6359_mtkaif_calibration_enable(cmpnt_codec);
@@ -403,6 +488,9 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd)
for (i = 0; i < MT8188_MTKAIF_MISO_NUM; i++)
param->mtkaif_phase_cycle[i] = mtkaif_phase_cycle[i];
+ if (pin_w)
+ dapm_pinctrl_event(pin_w, NULL, SND_SOC_DAPM_POST_PMD);
+
dev_dbg(afe->dev, "%s(), end, calibration ok %d\n",
__func__, param->mtkaif_calibration_ok);
@@ -450,8 +538,17 @@ enum {
DAI_LINK_ETDM3_OUT_BE,
DAI_LINK_PCM1_BE,
DAI_LINK_UL_SRC_BE,
+ DAI_LINK_REGULAR_LAST = DAI_LINK_UL_SRC_BE,
+ DAI_LINK_SOF_START,
+ DAI_LINK_SOF_DL2_BE = DAI_LINK_SOF_START,
+ DAI_LINK_SOF_DL3_BE,
+ DAI_LINK_SOF_UL4_BE,
+ DAI_LINK_SOF_UL5_BE,
+ DAI_LINK_SOF_END = DAI_LINK_SOF_UL5_BE,
};
+#define DAI_LINK_REGULAR_NUM (DAI_LINK_REGULAR_LAST + 1)
+
static int mt8188_dptx_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
@@ -482,7 +579,8 @@ static int mt8188_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
static int mt8188_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd)
{
- struct mt8188_mt6359_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card);
+ struct mt8188_mt6359_priv *priv = soc_card_data->mach_priv;
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
int ret = 0;
@@ -507,7 +605,8 @@ static int mt8188_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd)
static int mt8188_dptx_codec_init(struct snd_soc_pcm_runtime *rtd)
{
- struct mt8188_mt6359_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card);
+ struct mt8188_mt6359_priv *priv = soc_card_data->mach_priv;
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
int ret = 0;
@@ -627,7 +726,8 @@ static int mt8188_max98390_codec_init(struct snd_soc_pcm_runtime *rtd)
static int mt8188_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
- struct mt8188_mt6359_priv *priv = snd_soc_card_get_drvdata(card);
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card);
+ struct mt8188_mt6359_priv *priv = soc_card_data->mach_priv;
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_jack *jack = &priv->headset_jack;
int ret;
@@ -712,6 +812,33 @@ static int mt8188_nau8825_hw_params(struct snd_pcm_substream *substream,
static const struct snd_soc_ops mt8188_nau8825_ops = {
.hw_params = mt8188_nau8825_hw_params,
};
+
+static int mt8188_sof_be_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_component *cmpnt_afe = NULL;
+ struct snd_soc_pcm_runtime *runtime;
+
+ /* find afe component */
+ for_each_card_rtds(rtd->card, runtime) {
+ cmpnt_afe = snd_soc_rtdcom_lookup(runtime, AFE_PCM_NAME);
+ if (cmpnt_afe)
+ break;
+ }
+
+ if (cmpnt_afe && !pm_runtime_active(cmpnt_afe->dev)) {
+ dev_err(rtd->dev, "afe pm runtime is not active!!\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops mt8188_sof_be_ops = {
+ .hw_params = mt8188_sof_be_hw_params,
+};
+
static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = {
/* FE */
[DAI_LINK_DL2_FE] = {
@@ -982,11 +1109,42 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = {
.dpcm_capture = 1,
SND_SOC_DAILINK_REG(ul_src),
},
+
+ /* SOF BE */
+ [DAI_LINK_SOF_DL2_BE] = {
+ .name = "AFE_SOF_DL2",
+ .no_pcm = 1,
+ .dpcm_playback = 1,
+ .ops = &mt8188_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_DL2),
+ },
+ [DAI_LINK_SOF_DL3_BE] = {
+ .name = "AFE_SOF_DL3",
+ .no_pcm = 1,
+ .dpcm_playback = 1,
+ .ops = &mt8188_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_DL3),
+ },
+ [DAI_LINK_SOF_UL4_BE] = {
+ .name = "AFE_SOF_UL4",
+ .no_pcm = 1,
+ .dpcm_capture = 1,
+ .ops = &mt8188_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_UL4),
+ },
+ [DAI_LINK_SOF_UL5_BE] = {
+ .name = "AFE_SOF_UL5",
+ .no_pcm = 1,
+ .dpcm_capture = 1,
+ .ops = &mt8188_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_UL5),
+ },
};
static void mt8188_fixup_controls(struct snd_soc_card *card)
{
- struct mt8188_mt6359_priv *priv = snd_soc_card_get_drvdata(card);
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card);
+ struct mt8188_mt6359_priv *priv = soc_card_data->mach_priv;
struct mt8188_card_data *card_data = (struct mt8188_card_data *)priv->private_data;
struct snd_kcontrol *kctl;
@@ -1014,6 +1172,8 @@ static struct snd_soc_card mt8188_mt6359_soc_card = {
.num_links = ARRAY_SIZE(mt8188_mt6359_dai_links),
.dapm_widgets = mt8188_mt6359_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt8188_mt6359_widgets),
+ .dapm_routes = mt8188_mt6359_routes,
+ .num_dapm_routes = ARRAY_SIZE(mt8188_mt6359_routes),
.controls = mt8188_mt6359_controls,
.num_controls = ARRAY_SIZE(mt8188_mt6359_controls),
.fixup_controls = mt8188_fixup_controls,
@@ -1023,6 +1183,8 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &mt8188_mt6359_soc_card;
struct device_node *platform_node;
+ struct device_node *adsp_node;
+ struct mtk_soc_card_data *soc_card_data;
struct mt8188_mt6359_priv *priv;
struct mt8188_card_data *card_data;
struct snd_soc_dai_link *dai_link;
@@ -1043,21 +1205,64 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev)
if (!card->name)
card->name = card_data->name;
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
if (of_property_read_bool(pdev->dev.of_node, "audio-routing")) {
ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
if (ret)
return ret;
}
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ soc_card_data = devm_kzalloc(&pdev->dev, sizeof(*card_data), GFP_KERNEL);
+ if (!soc_card_data)
+ return -ENOMEM;
+
+ soc_card_data->mach_priv = priv;
+
+ adsp_node = of_parse_phandle(pdev->dev.of_node, "mediatek,adsp", 0);
+ if (adsp_node) {
+ struct mtk_sof_priv *sof_priv;
+
+ sof_priv = devm_kzalloc(&pdev->dev, sizeof(*sof_priv), GFP_KERNEL);
+ if (!sof_priv) {
+ ret = -ENOMEM;
+ goto err_adsp_node;
+ }
+ sof_priv->conn_streams = g_sof_conn_streams;
+ sof_priv->num_streams = ARRAY_SIZE(g_sof_conn_streams);
+ soc_card_data->sof_priv = sof_priv;
+ card->probe = mtk_sof_card_probe;
+ card->late_probe = mtk_sof_card_late_probe;
+ if (!card->topology_shortname_created) {
+ snprintf(card->topology_shortname, 32, "sof-%s", card->name);
+ card->topology_shortname_created = true;
+ }
+ card->name = card->topology_shortname;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "mediatek,dai-link")) {
+ ret = mtk_sof_dailink_parse_of(card, pdev->dev.of_node,
+ "mediatek,dai-link",
+ mt8188_mt6359_dai_links,
+ ARRAY_SIZE(mt8188_mt6359_dai_links));
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret, "Parse dai-link fail\n");
+ goto err_adsp_node;
+ }
+ } else {
+ if (!adsp_node)
+ card->num_links = DAI_LINK_REGULAR_NUM;
+ }
+
platform_node = of_parse_phandle(pdev->dev.of_node,
"mediatek,platform", 0);
if (!platform_node) {
- ret = -EINVAL;
- return dev_err_probe(&pdev->dev, ret, "Property 'platform' missing or invalid\n");
+ ret = dev_err_probe(&pdev->dev, -EINVAL,
+ "Property 'platform' missing or invalid\n");
+ goto err_adsp_node;
+
}
ret = parse_dai_link_info(card);
@@ -1065,8 +1270,12 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev)
goto err;
for_each_card_prelinks(card, i, dai_link) {
- if (!dai_link->platforms->name)
- dai_link->platforms->of_node = platform_node;
+ if (!dai_link->platforms->name) {
+ if (!strncmp(dai_link->name, "AFE_SOF", strlen("AFE_SOF")) && adsp_node)
+ dai_link->platforms->of_node = adsp_node;
+ else
+ dai_link->platforms->of_node = platform_node;
+ }
if (strcmp(dai_link->name, "DPTX_BE") == 0) {
if (strcmp(dai_link->codecs->dai_name, "snd-soc-dummy-dai"))
@@ -1109,7 +1318,7 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev)
}
priv->private_data = card_data;
- snd_soc_card_set_drvdata(card, priv);
+ snd_soc_card_set_drvdata(card, soc_card_data);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
@@ -1118,6 +1327,10 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev)
err:
of_node_put(platform_node);
clean_card_reference(card);
+
+err_adsp_node:
+ of_node_put(adsp_node);
+
return ret;
}
diff --git a/sound/soc/meson/axg-fifo.c b/sound/soc/meson/axg-fifo.c
index bccfb770b339..2e3d0108179b 100644
--- a/sound/soc/meson/axg-fifo.c
+++ b/sound/soc/meson/axg-fifo.c
@@ -31,7 +31,7 @@ static struct snd_pcm_hardware axg_fifo_hw = {
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
.formats = AXG_FIFO_FORMATS,
.rate_min = 5512,
- .rate_max = 192000,
+ .rate_max = 384000,
.channels_min = 1,
.channels_max = AXG_FIFO_CH_MAX,
.period_bytes_min = AXG_FIFO_BURST,
diff --git a/sound/soc/meson/axg-fifo.h b/sound/soc/meson/axg-fifo.h
index b63acd723c87..df528e8cb7c9 100644
--- a/sound/soc/meson/axg-fifo.h
+++ b/sound/soc/meson/axg-fifo.h
@@ -22,7 +22,7 @@ struct snd_soc_pcm_runtime;
#define AXG_FIFO_CH_MAX 128
#define AXG_FIFO_RATES (SNDRV_PCM_RATE_5512 | \
- SNDRV_PCM_RATE_8000_192000)
+ SNDRV_PCM_RATE_8000_384000)
#define AXG_FIFO_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_LE | \
diff --git a/sound/soc/meson/axg-tdm.h b/sound/soc/meson/axg-tdm.h
index 5774ce0916d4..42f7470b9a7f 100644
--- a/sound/soc/meson/axg-tdm.h
+++ b/sound/soc/meson/axg-tdm.h
@@ -16,7 +16,7 @@
#define AXG_TDM_NUM_LANES 4
#define AXG_TDM_CHANNEL_MAX 128
#define AXG_TDM_RATES (SNDRV_PCM_RATE_5512 | \
- SNDRV_PCM_RATE_8000_192000)
+ SNDRV_PCM_RATE_8000_384000)
#define AXG_TDM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_LE | \
diff --git a/sound/soc/starfive/Kconfig b/sound/soc/starfive/Kconfig
index fafb681f8c0a..279ac5c1d309 100644
--- a/sound/soc/starfive/Kconfig
+++ b/sound/soc/starfive/Kconfig
@@ -7,6 +7,15 @@ config SND_SOC_STARFIVE
the Starfive SoCs' Audio interfaces. You will also need to
select the audio interfaces to support below.
+config SND_SOC_JH7110_PWMDAC
+ tristate "JH7110 PWM-DAC device driver"
+ depends on HAVE_CLK && SND_SOC_STARFIVE
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select SND_SOC_SPDIF
+ help
+ Say Y or M if you want to add support for StarFive JH7110
+ PWM-DAC driver.
+
config SND_SOC_JH7110_TDM
tristate "JH7110 TDM device driver"
depends on HAVE_CLK && SND_SOC_STARFIVE
diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile
index f7d960211d72..9e958f70ef51 100644
--- a/sound/soc/starfive/Makefile
+++ b/sound/soc/starfive/Makefile
@@ -1,2 +1,3 @@
# StarFive Platform Support
+obj-$(CONFIG_SND_SOC_JH7110_PWMDAC) += jh7110_pwmdac.o
obj-$(CONFIG_SND_SOC_JH7110_TDM) += jh7110_tdm.o
diff --git a/sound/soc/starfive/jh7110_pwmdac.c b/sound/soc/starfive/jh7110_pwmdac.c
new file mode 100644
index 000000000000..033a9064f06b
--- /dev/null
+++ b/sound/soc/starfive/jh7110_pwmdac.c
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * jh7110_pwmdac.c -- StarFive JH7110 PWM-DAC driver
+ *
+ * Copyright (C) 2021-2023 StarFive Technology Co., Ltd.
+ *
+ * Authors: Jenny Zhang
+ * Curry Zhang
+ * Xingyu Wu <xingyu.wu@starfivetech.com>
+ * Hal Feng <hal.feng@starfivetech.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define JH7110_PWMDAC_WDATA 0x00
+#define JH7110_PWMDAC_CTRL 0x04
+ #define JH7110_PWMDAC_ENABLE BIT(0)
+ #define JH7110_PWMDAC_SHIFT BIT(1)
+ #define JH7110_PWMDAC_DUTY_CYCLE_SHIFT 2
+ #define JH7110_PWMDAC_DUTY_CYCLE_MASK GENMASK(3, 2)
+ #define JH7110_PWMDAC_CNT_N_SHIFT 4
+ #define JH7110_PWMDAC_CNT_N_MASK GENMASK(12, 4)
+ #define JH7110_PWMDAC_DATA_CHANGE BIT(13)
+ #define JH7110_PWMDAC_DATA_MODE BIT(14)
+ #define JH7110_PWMDAC_DATA_SHIFT_SHIFT 15
+ #define JH7110_PWMDAC_DATA_SHIFT_MASK GENMASK(17, 15)
+
+enum JH7110_PWMDAC_SHIFT_VAL {
+ PWMDAC_SHIFT_8 = 0,
+ PWMDAC_SHIFT_10,
+};
+
+enum JH7110_PWMDAC_DUTY_CYCLE_VAL {
+ PWMDAC_CYCLE_LEFT = 0,
+ PWMDAC_CYCLE_RIGHT,
+ PWMDAC_CYCLE_CENTER,
+};
+
+enum JH7110_PWMDAC_CNT_N_VAL {
+ PWMDAC_SAMPLE_CNT_1 = 1,
+ PWMDAC_SAMPLE_CNT_2,
+ PWMDAC_SAMPLE_CNT_3,
+ PWMDAC_SAMPLE_CNT_512 = 512, /* max */
+};
+
+enum JH7110_PWMDAC_DATA_CHANGE_VAL {
+ NO_CHANGE = 0,
+ CHANGE,
+};
+
+enum JH7110_PWMDAC_DATA_MODE_VAL {
+ UNSIGNED_DATA = 0,
+ INVERTER_DATA_MSB,
+};
+
+enum JH7110_PWMDAC_DATA_SHIFT_VAL {
+ PWMDAC_DATA_LEFT_SHIFT_BIT_0 = 0,
+ PWMDAC_DATA_LEFT_SHIFT_BIT_1,
+ PWMDAC_DATA_LEFT_SHIFT_BIT_2,
+ PWMDAC_DATA_LEFT_SHIFT_BIT_3,
+ PWMDAC_DATA_LEFT_SHIFT_BIT_4,
+ PWMDAC_DATA_LEFT_SHIFT_BIT_5,
+ PWMDAC_DATA_LEFT_SHIFT_BIT_6,
+ PWMDAC_DATA_LEFT_SHIFT_BIT_7,
+};
+
+struct jh7110_pwmdac_cfg {
+ enum JH7110_PWMDAC_SHIFT_VAL shift;
+ enum JH7110_PWMDAC_DUTY_CYCLE_VAL duty_cycle;
+ u16 cnt_n;
+ enum JH7110_PWMDAC_DATA_CHANGE_VAL data_change;
+ enum JH7110_PWMDAC_DATA_MODE_VAL data_mode;
+ enum JH7110_PWMDAC_DATA_SHIFT_VAL data_shift;
+};
+
+struct jh7110_pwmdac_dev {
+ void __iomem *base;
+ resource_size_t mapbase;
+ struct jh7110_pwmdac_cfg cfg;
+
+ struct clk_bulk_data clks[2];
+ struct reset_control *rst_apb;
+ struct device *dev;
+ struct snd_dmaengine_dai_dma_data play_dma_data;
+ u32 saved_ctrl;
+};
+
+static inline void jh7110_pwmdac_write_reg(void __iomem *io_base, int reg, u32 val)
+{
+ writel(val, io_base + reg);
+}
+
+static inline u32 jh7110_pwmdac_read_reg(void __iomem *io_base, int reg)
+{
+ return readl(io_base + reg);
+}
+
+static void jh7110_pwmdac_set_enable(struct jh7110_pwmdac_dev *dev, bool enable)
+{
+ u32 value;
+
+ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL);
+ if (enable)
+ value |= JH7110_PWMDAC_ENABLE;
+ else
+ value &= ~JH7110_PWMDAC_ENABLE;
+
+ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value);
+}
+
+static void jh7110_pwmdac_set_shift(struct jh7110_pwmdac_dev *dev)
+{
+ u32 value;
+
+ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL);
+ if (dev->cfg.shift == PWMDAC_SHIFT_8)
+ value &= ~JH7110_PWMDAC_SHIFT;
+ else if (dev->cfg.shift == PWMDAC_SHIFT_10)
+ value |= JH7110_PWMDAC_SHIFT;
+
+ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value);
+}
+
+static void jh7110_pwmdac_set_duty_cycle(struct jh7110_pwmdac_dev *dev)
+{
+ u32 value;
+
+ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL);
+ value &= ~JH7110_PWMDAC_DUTY_CYCLE_MASK;
+ value |= (dev->cfg.duty_cycle & 0x3) << JH7110_PWMDAC_DUTY_CYCLE_SHIFT;
+
+ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value);
+}
+
+static void jh7110_pwmdac_set_cnt_n(struct jh7110_pwmdac_dev *dev)
+{
+ u32 value;
+
+ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL);
+ value &= ~JH7110_PWMDAC_CNT_N_MASK;
+ value |= ((dev->cfg.cnt_n - 1) & 0x1ff) << JH7110_PWMDAC_CNT_N_SHIFT;
+
+ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value);
+}
+
+static void jh7110_pwmdac_set_data_change(struct jh7110_pwmdac_dev *dev)
+{
+ u32 value;
+
+ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL);
+ if (dev->cfg.data_change == NO_CHANGE)
+ value &= ~JH7110_PWMDAC_DATA_CHANGE;
+ else if (dev->cfg.data_change == CHANGE)
+ value |= JH7110_PWMDAC_DATA_CHANGE;
+
+ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value);
+}
+
+static void jh7110_pwmdac_set_data_mode(struct jh7110_pwmdac_dev *dev)
+{
+ u32 value;
+
+ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL);
+ if (dev->cfg.data_mode == UNSIGNED_DATA)
+ value &= ~JH7110_PWMDAC_DATA_MODE;
+ else if (dev->cfg.data_mode == INVERTER_DATA_MSB)
+ value |= JH7110_PWMDAC_DATA_MODE;
+
+ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value);
+}
+
+static void jh7110_pwmdac_set_data_shift(struct jh7110_pwmdac_dev *dev)
+{
+ u32 value;
+
+ value = jh7110_pwmdac_read_reg(dev->base, JH7110_PWMDAC_CTRL);
+ value &= ~JH7110_PWMDAC_DATA_SHIFT_MASK;
+ value |= (dev->cfg.data_shift & 0x7) << JH7110_PWMDAC_DATA_SHIFT_SHIFT;
+
+ jh7110_pwmdac_write_reg(dev->base, JH7110_PWMDAC_CTRL, value);
+}
+
+static void jh7110_pwmdac_set(struct jh7110_pwmdac_dev *dev)
+{
+ jh7110_pwmdac_set_shift(dev);
+ jh7110_pwmdac_set_duty_cycle(dev);
+ jh7110_pwmdac_set_cnt_n(dev);
+ jh7110_pwmdac_set_enable(dev, true);
+
+ jh7110_pwmdac_set_data_change(dev);
+ jh7110_pwmdac_set_data_mode(dev);
+ jh7110_pwmdac_set_data_shift(dev);
+}
+
+static void jh7110_pwmdac_stop(struct jh7110_pwmdac_dev *dev)
+{
+ jh7110_pwmdac_set_enable(dev, false);
+}
+
+static int jh7110_pwmdac_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai_link *dai_link = rtd->dai_link;
+
+ dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
+
+ return 0;
+}
+
+static int jh7110_pwmdac_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct jh7110_pwmdac_dev *dev = dev_get_drvdata(dai->dev);
+ unsigned long core_clk_rate;
+ int ret;
+
+ switch (params_rate(params)) {
+ case 8000:
+ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_3;
+ core_clk_rate = 6144000;
+ break;
+ case 11025:
+ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_2;
+ core_clk_rate = 5644800;
+ break;
+ case 16000:
+ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_3;
+ core_clk_rate = 12288000;
+ break;
+ case 22050:
+ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1;
+ core_clk_rate = 5644800;
+ break;
+ case 32000:
+ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1;
+ core_clk_rate = 8192000;
+ break;
+ case 44100:
+ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1;
+ core_clk_rate = 11289600;
+ break;
+ case 48000:
+ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1;
+ core_clk_rate = 12288000;
+ break;
+ default:
+ dev_err(dai->dev, "%d rate not supported\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ switch (params_channels(params)) {
+ case 1:
+ dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case 2:
+ dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ default:
+ dev_err(dai->dev, "%d channels not supported\n",
+ params_channels(params));
+ return -EINVAL;
+ }
+
+ /*
+ * The clock rate always rounds down when using clk_set_rate()
+ * so increase the rate a bit
+ */
+ core_clk_rate += 64;
+ jh7110_pwmdac_set(dev);
+
+ ret = clk_set_rate(dev->clks[1].clk, core_clk_rate);
+ if (ret)
+ return dev_err_probe(dai->dev, ret,
+ "failed to set rate %lu for core clock\n",
+ core_clk_rate);
+
+ return 0;
+}
+
+static int jh7110_pwmdac_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct jh7110_pwmdac_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ jh7110_pwmdac_set(dev);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ jh7110_pwmdac_stop(dev);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int jh7110_pwmdac_crg_enable(struct jh7110_pwmdac_dev *dev, bool enable)
+{
+ int ret;
+
+ if (enable) {
+ ret = clk_bulk_prepare_enable(ARRAY_SIZE(dev->clks), dev->clks);
+ if (ret)
+ return dev_err_probe(dev->dev, ret,
+ "failed to enable pwmdac clocks\n");
+
+ ret = reset_control_deassert(dev->rst_apb);
+ if (ret) {
+ dev_err(dev->dev, "failed to deassert pwmdac apb reset\n");
+ goto err_rst_apb;
+ }
+ } else {
+ clk_bulk_disable_unprepare(ARRAY_SIZE(dev->clks), dev->clks);
+ }
+
+ return 0;
+
+err_rst_apb:
+ clk_bulk_disable_unprepare(ARRAY_SIZE(dev->clks), dev->clks);
+
+ return ret;
+}
+
+static int jh7110_pwmdac_dai_probe(struct snd_soc_dai *dai)
+{
+ struct jh7110_pwmdac_dev *dev = dev_get_drvdata(dai->dev);
+
+ snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, NULL);
+ snd_soc_dai_set_drvdata(dai, dev);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops jh7110_pwmdac_dai_ops = {
+ .startup = jh7110_pwmdac_startup,
+ .hw_params = jh7110_pwmdac_hw_params,
+ .trigger = jh7110_pwmdac_trigger,
+};
+
+static const struct snd_soc_component_driver jh7110_pwmdac_component = {
+ .name = "jh7110-pwmdac",
+};
+
+static struct snd_soc_dai_driver jh7110_pwmdac_dai = {
+ .name = "jh7110-pwmdac",
+ .id = 0,
+ .probe = jh7110_pwmdac_dai_probe,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &jh7110_pwmdac_dai_ops,
+};
+
+static int jh7110_pwmdac_runtime_suspend(struct device *dev)
+{
+ struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev);
+
+ return jh7110_pwmdac_crg_enable(pwmdac, false);
+}
+
+static int jh7110_pwmdac_runtime_resume(struct device *dev)
+{
+ struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev);
+
+ return jh7110_pwmdac_crg_enable(pwmdac, true);
+}
+
+static int jh7110_pwmdac_system_suspend(struct device *dev)
+{
+ struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev);
+
+ /* save the CTRL register value */
+ pwmdac->saved_ctrl = jh7110_pwmdac_read_reg(pwmdac->base,
+ JH7110_PWMDAC_CTRL);
+ return pm_runtime_force_suspend(dev);
+}
+
+static int jh7110_pwmdac_system_resume(struct device *dev)
+{
+ struct jh7110_pwmdac_dev *pwmdac = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ /* restore the CTRL register value */
+ jh7110_pwmdac_write_reg(pwmdac->base, JH7110_PWMDAC_CTRL,
+ pwmdac->saved_ctrl);
+ return 0;
+}
+
+static const struct dev_pm_ops jh7110_pwmdac_pm_ops = {
+ RUNTIME_PM_OPS(jh7110_pwmdac_runtime_suspend,
+ jh7110_pwmdac_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(jh7110_pwmdac_system_suspend,
+ jh7110_pwmdac_system_resume)
+};
+
+static void jh7110_pwmdac_init_params(struct jh7110_pwmdac_dev *dev)
+{
+ dev->cfg.shift = PWMDAC_SHIFT_8;
+ dev->cfg.duty_cycle = PWMDAC_CYCLE_CENTER;
+ dev->cfg.cnt_n = PWMDAC_SAMPLE_CNT_1;
+ dev->cfg.data_change = NO_CHANGE;
+ dev->cfg.data_mode = INVERTER_DATA_MSB;
+ dev->cfg.data_shift = PWMDAC_DATA_LEFT_SHIFT_BIT_0;
+
+ dev->play_dma_data.addr = dev->mapbase + JH7110_PWMDAC_WDATA;
+ dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dev->play_dma_data.fifo_size = 1;
+ dev->play_dma_data.maxburst = 16;
+}
+
+static int jh7110_pwmdac_probe(struct platform_device *pdev)
+{
+ struct jh7110_pwmdac_dev *dev;
+ struct resource *res;
+ int ret;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(dev->base))
+ return PTR_ERR(dev->base);
+
+ dev->mapbase = res->start;
+
+ dev->clks[0].id = "apb";
+ dev->clks[1].id = "core";
+
+ ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(dev->clks), dev->clks);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to get pwmdac clocks\n");
+
+ dev->rst_apb = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(dev->rst_apb))
+ return dev_err_probe(&pdev->dev, PTR_ERR(dev->rst_apb),
+ "failed to get pwmdac apb reset\n");
+
+ jh7110_pwmdac_init_params(dev);
+
+ dev->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, dev);
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &jh7110_pwmdac_component,
+ &jh7110_pwmdac_dai, 1);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to register dai\n");
+
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n");
+
+ pm_runtime_enable(dev->dev);
+ if (!pm_runtime_enabled(&pdev->dev)) {
+ ret = jh7110_pwmdac_runtime_resume(&pdev->dev);
+ if (ret)
+ goto err_pm_disable;
+ }
+
+ return 0;
+
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static int jh7110_pwmdac_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+static const struct of_device_id jh7110_pwmdac_of_match[] = {
+ { .compatible = "starfive,jh7110-pwmdac" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, jh7110_pwmdac_of_match);
+
+static struct platform_driver jh7110_pwmdac_driver = {
+ .driver = {
+ .name = "jh7110-pwmdac",
+ .of_match_table = jh7110_pwmdac_of_match,
+ .pm = pm_ptr(&jh7110_pwmdac_pm_ops),
+ },
+ .probe = jh7110_pwmdac_probe,
+ .remove = jh7110_pwmdac_remove,
+};
+module_platform_driver(jh7110_pwmdac_driver);
+
+MODULE_AUTHOR("Jenny Zhang");
+MODULE_AUTHOR("Curry Zhang");
+MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>");
+MODULE_AUTHOR("Hal Feng <hal.feng@starfivetech.com>");
+MODULE_DESCRIPTION("StarFive JH7110 PWM-DAC driver");
+MODULE_LICENSE("GPL");