3334 lines
99 KiB
Diff
3334 lines
99 KiB
Diff
From f358a7f43e72969642cce5d881580cb8106b51ee Mon Sep 17 00:00:00 2001
|
|
From: yangqiang <yangqiang1@eswincomputing.com>
|
|
Date: Tue, 21 May 2024 09:32:04 +0800
|
|
Subject: [PATCH 018/219] feat:Porting eswin audio to 6.6 kernel
|
|
|
|
Changelogs:
|
|
1. Porting eswin audio to 6.6 kernel.
|
|
---
|
|
arch/riscv/configs/win2030_defconfig | 5 +-
|
|
sound/soc/Kconfig | 1 +
|
|
sound/soc/Makefile | 1 +
|
|
sound/soc/codecs/Kconfig | 1 +
|
|
sound/soc/codecs/Makefile | 1 +
|
|
sound/soc/codecs/eswin/Kconfig | 20 +
|
|
sound/soc/codecs/eswin/Makefile | 3 +
|
|
sound/soc/codecs/eswin/es8328-i2c.c | 65 ++
|
|
sound/soc/codecs/eswin/es8328.c | 1101 ++++++++++++++++++++++++++
|
|
sound/soc/codecs/eswin/es8328.h | 299 +++++++
|
|
sound/soc/eswin/Kconfig | 11 +
|
|
sound/soc/eswin/Makefile | 5 +
|
|
sound/soc/eswin/esw-audio-proc.c | 488 ++++++++++++
|
|
sound/soc/eswin/esw-audio-proc.h | 23 +
|
|
sound/soc/eswin/esw-i2s.c | 973 +++++++++++++++++++++++
|
|
sound/soc/eswin/esw-i2s.h | 181 +++++
|
|
16 files changed, 3177 insertions(+), 1 deletion(-)
|
|
create mode 100755 sound/soc/codecs/eswin/Kconfig
|
|
create mode 100755 sound/soc/codecs/eswin/Makefile
|
|
create mode 100644 sound/soc/codecs/eswin/es8328-i2c.c
|
|
create mode 100644 sound/soc/codecs/eswin/es8328.c
|
|
create mode 100644 sound/soc/codecs/eswin/es8328.h
|
|
create mode 100644 sound/soc/eswin/Kconfig
|
|
create mode 100644 sound/soc/eswin/Makefile
|
|
create mode 100644 sound/soc/eswin/esw-audio-proc.c
|
|
create mode 100644 sound/soc/eswin/esw-audio-proc.h
|
|
create mode 100755 sound/soc/eswin/esw-i2s.c
|
|
create mode 100644 sound/soc/eswin/esw-i2s.h
|
|
|
|
diff --git a/arch/riscv/configs/win2030_defconfig b/arch/riscv/configs/win2030_defconfig
|
|
index 9149eabecee2..5d25e67b935d 100644
|
|
--- a/arch/riscv/configs/win2030_defconfig
|
|
+++ b/arch/riscv/configs/win2030_defconfig
|
|
@@ -177,7 +177,10 @@ CONFIG_SND=y
|
|
CONFIG_SND_SOC=y
|
|
CONFIG_SND_SOC_SOF_TOPLEVEL=y
|
|
CONFIG_SND_SOC_SOF_OF=y
|
|
-CONFIG_SND_SOC_ES8316=y
|
|
+CONFIG_SND_ESWIN_DW_I2S=y
|
|
+CONFIG_SND_SOC_HDMI_CODEC=y
|
|
+CONFIG_ESWIN_SND_SOC_CODECS=y
|
|
+CONFIG_ESWIN_SND_ES8388_CODEC=y
|
|
CONFIG_SND_SIMPLE_CARD=y
|
|
CONFIG_SND_AUDIO_GRAPH_CARD=y
|
|
CONFIG_USB_ULPI_BUS=y
|
|
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
|
|
index 439fa631c342..dfd26f13970c 100644
|
|
--- a/sound/soc/Kconfig
|
|
+++ b/sound/soc/Kconfig
|
|
@@ -114,6 +114,7 @@ source "sound/soc/uniphier/Kconfig"
|
|
source "sound/soc/ux500/Kconfig"
|
|
source "sound/soc/xilinx/Kconfig"
|
|
source "sound/soc/xtensa/Kconfig"
|
|
+source "sound/soc/eswin/Kconfig"
|
|
|
|
# Supported codecs
|
|
source "sound/soc/codecs/Kconfig"
|
|
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
|
|
index 8376fdb217ed..7c3b9197e7b2 100644
|
|
--- a/sound/soc/Makefile
|
|
+++ b/sound/soc/Makefile
|
|
@@ -71,3 +71,4 @@ obj-$(CONFIG_SND_SOC) += uniphier/
|
|
obj-$(CONFIG_SND_SOC) += ux500/
|
|
obj-$(CONFIG_SND_SOC) += xilinx/
|
|
obj-$(CONFIG_SND_SOC) += xtensa/
|
|
+obj-$(CONFIG_SND_SOC) += eswin/
|
|
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
|
|
index f1e1dbc509f6..319d89477279 100644
|
|
--- a/sound/soc/codecs/Kconfig
|
|
+++ b/sound/soc/codecs/Kconfig
|
|
@@ -2404,4 +2404,5 @@ config SND_SOC_LPASS_TX_MACRO
|
|
select SND_SOC_LPASS_MACRO_COMMON
|
|
tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)"
|
|
|
|
+source "sound/soc/codecs/eswin/Kconfig"
|
|
endmenu
|
|
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
|
|
index a87e56938ce5..ea3f39cf3d70 100644
|
|
--- a/sound/soc/codecs/Makefile
|
|
+++ b/sound/soc/codecs/Makefile
|
|
@@ -772,3 +772,4 @@ obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO) += snd-soc-lpass-tx-macro.o
|
|
|
|
# Mux
|
|
obj-$(CONFIG_SND_SOC_SIMPLE_MUX) += snd-soc-simple-mux.o
|
|
+obj-$(CONFIG_ESWIN_SND_SOC_CODECS) += eswin/
|
|
diff --git a/sound/soc/codecs/eswin/Kconfig b/sound/soc/codecs/eswin/Kconfig
|
|
new file mode 100755
|
|
index 000000000000..d3c7039aa8e9
|
|
--- /dev/null
|
|
+++ b/sound/soc/codecs/eswin/Kconfig
|
|
@@ -0,0 +1,20 @@
|
|
+menuconfig ESWIN_SND_SOC_CODECS
|
|
+ bool "ESWIN CODEC drivers"
|
|
+ default n
|
|
+ help
|
|
+ Say Y or M if you want to add support for codecs attached to
|
|
+ the ESWIN Asoc interface. You will also need
|
|
+ to select the audio interfaces to support below.
|
|
+
|
|
+#if ESWIN_SND_SOC_CODECS
|
|
+
|
|
+config ESWIN_SND_ES8388_CODEC
|
|
+ bool "ESWIN Audio es8388 codec"
|
|
+ depends on ESWIN_SND_SOC_CODECS
|
|
+ default n
|
|
+ help
|
|
+ ESWIN Audio codec,
|
|
+ es8388 codec,
|
|
+ this codec is internal
|
|
+
|
|
+#endif #ESWIN_SND_SOC_CODECS
|
|
diff --git a/sound/soc/codecs/eswin/Makefile b/sound/soc/codecs/eswin/Makefile
|
|
new file mode 100755
|
|
index 000000000000..ee696d62096c
|
|
--- /dev/null
|
|
+++ b/sound/soc/codecs/eswin/Makefile
|
|
@@ -0,0 +1,3 @@
|
|
+esw_es8328_codec-objs := es8328-i2c.o es8328.o
|
|
+
|
|
+obj-$(CONFIG_ESWIN_SND_ES8388_CODEC) += esw_es8328_codec.o
|
|
\ No newline at end of file
|
|
diff --git a/sound/soc/codecs/eswin/es8328-i2c.c b/sound/soc/codecs/eswin/es8328-i2c.c
|
|
new file mode 100644
|
|
index 000000000000..d6756c46a4a5
|
|
--- /dev/null
|
|
+++ b/sound/soc/codecs/eswin/es8328-i2c.c
|
|
@@ -0,0 +1,65 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * es8328-i2c.c -- ES8328 ALSA SoC I2C Audio driver
|
|
+ *
|
|
+ * Copyright 2014 Sutajio Ko-Usagi PTE LTD
|
|
+ *
|
|
+ * Author: Sean Cross <xobs@kosagi.com>
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * Copyright (C) 2021 ESWIN, Inc. All rights reserved.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
+ * more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/regmap.h>
|
|
+
|
|
+#include <sound/soc.h>
|
|
+
|
|
+#include "es8328.h"
|
|
+
|
|
+static const struct i2c_device_id es8328_id[] = {
|
|
+ { "es8328", 0 },
|
|
+ { "es8388", 0 },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(i2c, es8328_id);
|
|
+
|
|
+static const struct of_device_id es8328_of_match[] = {
|
|
+ { .compatible = "eswin,es8388", },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, es8328_of_match);
|
|
+
|
|
+static int es8328_i2c_probe(struct i2c_client *i2c)
|
|
+{
|
|
+ return es8328_probe(&i2c->dev,
|
|
+ devm_regmap_init_i2c(i2c, &es8328_regmap_config));
|
|
+}
|
|
+
|
|
+static struct i2c_driver es8328_i2c_driver = {
|
|
+ .driver = {
|
|
+ .name = "es8328",
|
|
+ .of_match_table = es8328_of_match,
|
|
+ },
|
|
+ .probe = es8328_i2c_probe,
|
|
+ .id_table = es8328_id,
|
|
+};
|
|
+
|
|
+module_i2c_driver(es8328_i2c_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("ASoC ES8328 audio CODEC I2C driver");
|
|
+MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/sound/soc/codecs/eswin/es8328.c b/sound/soc/codecs/eswin/es8328.c
|
|
new file mode 100644
|
|
index 000000000000..2c7a4031fa44
|
|
--- /dev/null
|
|
+++ b/sound/soc/codecs/eswin/es8328.c
|
|
@@ -0,0 +1,1101 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * es8328.c -- ES8328 ALSA SoC Audio driver
|
|
+ *
|
|
+ * Copyright 2014 Sutajio Ko-Usagi PTE LTD
|
|
+ *
|
|
+ * Author: Sean Cross <xobs@kosagi.com>
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * Copyright (C) 2021 ESWIN, Inc. All rights reserved.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
+ * more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/pm.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <sound/core.h>
|
|
+#include <sound/initval.h>
|
|
+#include <sound/pcm.h>
|
|
+#include <sound/pcm_params.h>
|
|
+#include <sound/soc.h>
|
|
+#include <sound/tlv.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include "es8328.h"
|
|
+
|
|
+#define MIN_CHANNEL_NUM 2
|
|
+#define MAX_CHANNEL_NUM 2
|
|
+
|
|
+static const unsigned int rates_12288[] = {
|
|
+ 8000, 12000, 16000, 24000, 32000, 48000, 96000,
|
|
+};
|
|
+
|
|
+static const int ratios_12288[] = {
|
|
+ 10, 7, 6, 4, 3, 2, 0,
|
|
+};
|
|
+
|
|
+static const struct snd_pcm_hw_constraint_list constraints_12288 = {
|
|
+ .count = ARRAY_SIZE(rates_12288),
|
|
+ .list = rates_12288,
|
|
+};
|
|
+
|
|
+static const unsigned int rates_11289[] = {
|
|
+ 8018, 11025, 22050, 44100, 88200,
|
|
+};
|
|
+
|
|
+static const int ratios_11289[] = {
|
|
+ 9, 7, 4, 2, 0,
|
|
+};
|
|
+
|
|
+static const struct snd_pcm_hw_constraint_list constraints_11289 = {
|
|
+ .count = ARRAY_SIZE(rates_11289),
|
|
+ .list = rates_11289,
|
|
+};
|
|
+
|
|
+/* regulator supplies for sgtl5000, VDDD is an optional external supply */
|
|
+enum sgtl5000_regulator_supplies {
|
|
+ DVDD,
|
|
+ AVDD,
|
|
+ PVDD,
|
|
+ HPVDD,
|
|
+ ES8328_SUPPLY_NUM
|
|
+};
|
|
+
|
|
+/* vddd is optional supply */
|
|
+static const char * const supply_names[ES8328_SUPPLY_NUM] = {
|
|
+ "DVDD",
|
|
+ "AVDD",
|
|
+ "PVDD",
|
|
+ "HPVDD",
|
|
+};
|
|
+
|
|
+#define ES8328_RATES (SNDRV_PCM_RATE_192000 | \
|
|
+ SNDRV_PCM_RATE_96000 | \
|
|
+ SNDRV_PCM_RATE_88200 | \
|
|
+ SNDRV_PCM_RATE_8000_48000)
|
|
+#define ES8328_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
|
|
+ SNDRV_PCM_FMTBIT_S18_3LE | \
|
|
+ SNDRV_PCM_FMTBIT_S20_3LE | \
|
|
+ SNDRV_PCM_FMTBIT_S24_LE | \
|
|
+ SNDRV_PCM_FMTBIT_S32_LE)
|
|
+
|
|
+struct es8328_priv {
|
|
+ struct regmap *regmap;
|
|
+ struct clk *clk;
|
|
+ int playback_fs;
|
|
+ bool deemph;
|
|
+ int mclkdiv2;
|
|
+ const struct snd_pcm_hw_constraint_list *sysclk_constraints;
|
|
+ const int *mclk_ratios;
|
|
+ bool provider;
|
|
+ struct regulator_bulk_data supplies[ES8328_SUPPLY_NUM];
|
|
+
|
|
+ u32 eswin_plat;
|
|
+ struct snd_soc_component *component;
|
|
+ struct gpio_desc *front_jack_gpio;
|
|
+ struct gpio_desc *back_jack_gpio;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * ES8328 Controls
|
|
+ */
|
|
+
|
|
+static const char * const adcpol_txt[] = {"Normal", "L Invert", "R Invert",
|
|
+ "L + R Invert"};
|
|
+static SOC_ENUM_SINGLE_DECL(adcpol,
|
|
+ ES8328_ADCCONTROL6, 6, adcpol_txt);
|
|
+
|
|
+static const DECLARE_TLV_DB_SCALE(play_tlv, -3000, 100, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(dac_adc_tlv, -9600, 50, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 300, 0);
|
|
+
|
|
+static const struct {
|
|
+ int rate;
|
|
+ unsigned int val;
|
|
+} deemph_settings[] = {
|
|
+ { 0, ES8328_DACCONTROL6_DEEMPH_OFF },
|
|
+ { 32000, ES8328_DACCONTROL6_DEEMPH_32k },
|
|
+ { 44100, ES8328_DACCONTROL6_DEEMPH_44_1k },
|
|
+ { 48000, ES8328_DACCONTROL6_DEEMPH_48k },
|
|
+};
|
|
+
|
|
+static int es8328_set_deemph(struct snd_soc_component *component)
|
|
+{
|
|
+ struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component);
|
|
+ int val, i, best;
|
|
+
|
|
+ /*
|
|
+ * If we're using deemphasis select the nearest available sample
|
|
+ * rate.
|
|
+ */
|
|
+ if (es8328->deemph) {
|
|
+ best = 0;
|
|
+ for (i = 1; i < ARRAY_SIZE(deemph_settings); i++) {
|
|
+ if (abs(deemph_settings[i].rate - es8328->playback_fs) <
|
|
+ abs(deemph_settings[best].rate - es8328->playback_fs))
|
|
+ best = i;
|
|
+ }
|
|
+
|
|
+ val = deemph_settings[best].val;
|
|
+ } else {
|
|
+ val = ES8328_DACCONTROL6_DEEMPH_OFF;
|
|
+ }
|
|
+
|
|
+ dev_dbg(component->dev, "Set deemphasis %d\n", val);
|
|
+
|
|
+ return snd_soc_component_update_bits(component, ES8328_DACCONTROL6,
|
|
+ ES8328_DACCONTROL6_DEEMPH_MASK, val);
|
|
+}
|
|
+
|
|
+static int es8328_get_deemph(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
+ struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ ucontrol->value.integer.value[0] = es8328->deemph;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int es8328_put_deemph(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
+ struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component);
|
|
+ unsigned int deemph = ucontrol->value.integer.value[0];
|
|
+ int ret;
|
|
+
|
|
+ if (deemph > 1)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (es8328->deemph == deemph)
|
|
+ return 0;
|
|
+
|
|
+ ret = es8328_set_deemph(component);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ es8328->deemph = deemph;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+static int dump_flag = 0;
|
|
+static u32 g_reg = 1;
|
|
+static u32 g_get_cnt = 0;
|
|
+static u32 g_put_cnt = 0;
|
|
+
|
|
+int esw_codec_dump_get(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ ucontrol->value.integer.value[0] = dump_flag;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int esw_codec_dump_info(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
|
+ uinfo->count = 1;
|
|
+ uinfo->value.integer.min = 0;
|
|
+ uinfo->value.integer.max = 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int esw_codec_dump_put(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ int dump_onoff = ucontrol->value.integer.value[0];
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ u32 reg, ret, i;
|
|
+
|
|
+ printk("codec dump onoff:%d\n", dump_onoff);
|
|
+ dump_flag = dump_onoff;
|
|
+ if (dump_onoff == true) {
|
|
+ printk("statr codec dump\n");
|
|
+ for (i = 0; i < 53; i++) {
|
|
+ ret = regmap_read(component->regmap, i, ®);
|
|
+ if (ret != 0) {
|
|
+ printk("reag reg[%d] failed!\n", i);
|
|
+ }
|
|
+ printk("reg[%d]:0x%x\n", i, reg);
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int esw_codec_reg_get(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ g_get_cnt++;
|
|
+
|
|
+ if (g_get_cnt == 1) {
|
|
+ ucontrol->value.integer.value[0] = g_reg;
|
|
+ } else {
|
|
+ ret = regmap_read(component->regmap, g_reg, &val);
|
|
+ if (ret != 0) {
|
|
+ printk("read reg[%d] failed!\n", g_reg);
|
|
+ }
|
|
+ printk("codec read reg[%d]:0x%x\n", g_reg, val);
|
|
+ ucontrol->value.integer.value[1] = val;
|
|
+ g_get_cnt = 0;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int esw_codec_reg_info(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
+ uinfo->count = 2;
|
|
+ uinfo->value.integer.min = 0;
|
|
+ uinfo->value.integer.max = 256;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int esw_codec_reg_put(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ int ret;
|
|
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
+
|
|
+ g_put_cnt++;
|
|
+
|
|
+ if (g_put_cnt == 1) {
|
|
+ g_reg = ucontrol->value.integer.value[0];
|
|
+ } else {
|
|
+ printk("codec write reg:%d, val:0x%x\n", g_reg, (u32)ucontrol->value.integer.value[1]);
|
|
+ ret = regmap_write(component->regmap, g_reg, (u32)ucontrol->value.integer.value[1]);
|
|
+ if (ret != 0) {
|
|
+ printk("write reg[%d] failed!\n", g_reg);
|
|
+ }
|
|
+ g_put_cnt = 0;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new es8328_snd_controls[] = {
|
|
+ SOC_DOUBLE_R_TLV("Capture Digital Volume",
|
|
+ ES8328_ADCCONTROL8, ES8328_ADCCONTROL9,
|
|
+ 0, 0xc0, 1, dac_adc_tlv),
|
|
+ SOC_SINGLE("Capture ZC Switch", ES8328_ADCCONTROL7, 5, 1, 0),
|
|
+
|
|
+ SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
|
|
+ es8328_get_deemph, es8328_put_deemph),
|
|
+
|
|
+ SOC_ENUM("Capture Polarity", adcpol),
|
|
+
|
|
+ SOC_SINGLE_TLV("Left Mixer Left Bypass Volume",
|
|
+ ES8328_DACCONTROL17, 3, 7, 1, bypass_tlv),
|
|
+ SOC_SINGLE_TLV("Left Mixer Right Bypass Volume",
|
|
+ ES8328_DACCONTROL19, 3, 7, 1, bypass_tlv),
|
|
+ SOC_SINGLE_TLV("Right Mixer Left Bypass Volume",
|
|
+ ES8328_DACCONTROL18, 3, 7, 1, bypass_tlv),
|
|
+ SOC_SINGLE_TLV("Right Mixer Right Bypass Volume",
|
|
+ ES8328_DACCONTROL20, 3, 7, 1, bypass_tlv),
|
|
+
|
|
+ SOC_DOUBLE_R_TLV("PCM Volume",
|
|
+ ES8328_LDACVOL, ES8328_RDACVOL,
|
|
+ 0, ES8328_LDACVOL_MAX, 1, dac_adc_tlv),
|
|
+
|
|
+ SOC_DOUBLE_R_TLV("Output 1 Playback Volume",
|
|
+ ES8328_LOUT1VOL, ES8328_ROUT1VOL,
|
|
+ 0, ES8328_OUT1VOL_MAX, 0, play_tlv),
|
|
+
|
|
+ SOC_DOUBLE_R_TLV("Output 2 Playback Volume",
|
|
+ ES8328_LOUT2VOL, ES8328_ROUT2VOL,
|
|
+ 0, ES8328_OUT2VOL_MAX, 0, play_tlv),
|
|
+
|
|
+ SOC_DOUBLE_TLV("Mic PGA Volume", ES8328_ADCCONTROL1,
|
|
+ 4, 0, 8, 0, mic_tlv),
|
|
+ {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
|
+ .name = "Reg Dump",
|
|
+ .index = 0,
|
|
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
+ .info = esw_codec_dump_info,
|
|
+ .get = esw_codec_dump_get,
|
|
+ .put = esw_codec_dump_put,
|
|
+ },
|
|
+ {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "Reg Write",
|
|
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
+ .info = esw_codec_reg_info,
|
|
+ .get = esw_codec_reg_get,
|
|
+ .put = esw_codec_reg_put,
|
|
+ }
|
|
+};
|
|
+
|
|
+/*
|
|
+ * DAPM Controls
|
|
+ */
|
|
+
|
|
+static const char * const es8328_line_texts[] = {
|
|
+ "Line 1", "Line 2", "PGA", "Differential"};
|
|
+
|
|
+static const struct soc_enum es8328_lline_enum =
|
|
+ SOC_ENUM_SINGLE(ES8328_DACCONTROL16, 3,
|
|
+ ARRAY_SIZE(es8328_line_texts),
|
|
+ es8328_line_texts);
|
|
+static const struct snd_kcontrol_new es8328_left_line_controls =
|
|
+ SOC_DAPM_ENUM("Route", es8328_lline_enum);
|
|
+
|
|
+static const struct soc_enum es8328_rline_enum =
|
|
+ SOC_ENUM_SINGLE(ES8328_DACCONTROL16, 0,
|
|
+ ARRAY_SIZE(es8328_line_texts),
|
|
+ es8328_line_texts);
|
|
+static const struct snd_kcontrol_new es8328_right_line_controls =
|
|
+ SOC_DAPM_ENUM("Route", es8328_rline_enum);
|
|
+
|
|
+/* Left Mixer */
|
|
+static const struct snd_kcontrol_new es8328_left_mixer_controls[] = {
|
|
+ SOC_DAPM_SINGLE("Playback Switch", ES8328_DACCONTROL17, 7, 1, 0),
|
|
+ SOC_DAPM_SINGLE("Left Bypass Switch", ES8328_DACCONTROL17, 6, 1, 0),
|
|
+ SOC_DAPM_SINGLE("Right Playback Switch", ES8328_DACCONTROL18, 7, 1, 0),
|
|
+ SOC_DAPM_SINGLE("Right Bypass Switch", ES8328_DACCONTROL18, 6, 1, 0),
|
|
+};
|
|
+
|
|
+/* Right Mixer */
|
|
+static const struct snd_kcontrol_new es8328_right_mixer_controls[] = {
|
|
+ SOC_DAPM_SINGLE("Left Playback Switch", ES8328_DACCONTROL19, 7, 1, 0),
|
|
+ SOC_DAPM_SINGLE("Left Bypass Switch", ES8328_DACCONTROL19, 6, 1, 0),
|
|
+ SOC_DAPM_SINGLE("Playback Switch", ES8328_DACCONTROL20, 7, 1, 0),
|
|
+ SOC_DAPM_SINGLE("Right Bypass Switch", ES8328_DACCONTROL20, 6, 1, 0),
|
|
+};
|
|
+
|
|
+static const char * const es8328_pga_sel[] = {
|
|
+ "Line 1", "Line 2", "Line 3", "Differential"};
|
|
+
|
|
+/* Left PGA Mux */
|
|
+static const struct soc_enum es8328_lpga_enum =
|
|
+ SOC_ENUM_SINGLE(ES8328_ADCCONTROL2, 6,
|
|
+ ARRAY_SIZE(es8328_pga_sel),
|
|
+ es8328_pga_sel);
|
|
+static const struct snd_kcontrol_new es8328_left_pga_controls =
|
|
+ SOC_DAPM_ENUM("Route", es8328_lpga_enum);
|
|
+
|
|
+/* Right PGA Mux */
|
|
+static const struct soc_enum es8328_rpga_enum =
|
|
+ SOC_ENUM_SINGLE(ES8328_ADCCONTROL2, 4,
|
|
+ ARRAY_SIZE(es8328_pga_sel),
|
|
+ es8328_pga_sel);
|
|
+static const struct snd_kcontrol_new es8328_right_pga_controls =
|
|
+ SOC_DAPM_ENUM("Route", es8328_rpga_enum);
|
|
+
|
|
+/* Differential Mux */
|
|
+static const char * const es8328_diff_sel[] = {"Line 1", "Line 2"};
|
|
+static SOC_ENUM_SINGLE_DECL(diffmux,
|
|
+ ES8328_ADCCONTROL3, 7, es8328_diff_sel);
|
|
+static const struct snd_kcontrol_new es8328_diffmux_controls =
|
|
+ SOC_DAPM_ENUM("Route", diffmux);
|
|
+
|
|
+/* Mono ADC Mux */
|
|
+static const char * const es8328_mono_mux[] = {"Stereo", "Mono (Left)",
|
|
+ "Mono (Right)", "Digital Mono"};
|
|
+static SOC_ENUM_SINGLE_DECL(monomux,
|
|
+ ES8328_ADCCONTROL3, 3, es8328_mono_mux);
|
|
+static const struct snd_kcontrol_new es8328_monomux_controls =
|
|
+ SOC_DAPM_ENUM("Route", monomux);
|
|
+
|
|
+static const struct snd_soc_dapm_widget es8328_dapm_widgets[] = {
|
|
+ SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0,
|
|
+ &es8328_diffmux_controls),
|
|
+ SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0,
|
|
+ &es8328_monomux_controls),
|
|
+ SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0,
|
|
+ &es8328_monomux_controls),
|
|
+
|
|
+ SND_SOC_DAPM_MUX("Left PGA Mux", ES8328_ADCPOWER,
|
|
+ ES8328_ADCPOWER_AINL_OFF, 1,
|
|
+ &es8328_left_pga_controls),
|
|
+ SND_SOC_DAPM_MUX("Right PGA Mux", ES8328_ADCPOWER,
|
|
+ ES8328_ADCPOWER_AINR_OFF, 1,
|
|
+ &es8328_right_pga_controls),
|
|
+
|
|
+ SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0,
|
|
+ &es8328_left_line_controls),
|
|
+ SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0,
|
|
+ &es8328_right_line_controls),
|
|
+
|
|
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", ES8328_ADCPOWER,
|
|
+ ES8328_ADCPOWER_ADCR_OFF, 1),
|
|
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", ES8328_ADCPOWER,
|
|
+ ES8328_ADCPOWER_ADCL_OFF, 1),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("Mic Bias", ES8328_ADCPOWER,
|
|
+ ES8328_ADCPOWER_MIC_BIAS_OFF, 1, NULL, 0),
|
|
+ SND_SOC_DAPM_SUPPLY("Mic Bias Gen", ES8328_ADCPOWER,
|
|
+ ES8328_ADCPOWER_ADC_BIAS_GEN_OFF, 1, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("DAC STM", ES8328_CHIPPOWER,
|
|
+ ES8328_CHIPPOWER_DACSTM_RESET, 1, NULL, 0),
|
|
+ SND_SOC_DAPM_SUPPLY("ADC STM", ES8328_CHIPPOWER,
|
|
+ ES8328_CHIPPOWER_ADCSTM_RESET, 1, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("DAC DIG", ES8328_CHIPPOWER,
|
|
+ ES8328_CHIPPOWER_DACDIG_OFF, 1, NULL, 0),
|
|
+ SND_SOC_DAPM_SUPPLY("ADC DIG", ES8328_CHIPPOWER,
|
|
+ ES8328_CHIPPOWER_ADCDIG_OFF, 1, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("DAC DLL", ES8328_CHIPPOWER,
|
|
+ ES8328_CHIPPOWER_DACDLL_OFF, 1, NULL, 0),
|
|
+ SND_SOC_DAPM_SUPPLY("ADC DLL", ES8328_CHIPPOWER,
|
|
+ ES8328_CHIPPOWER_ADCDLL_OFF, 1, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_SUPPLY("ADC Vref", ES8328_CHIPPOWER,
|
|
+ ES8328_CHIPPOWER_ADCVREF_OFF, 1, NULL, 0),
|
|
+ SND_SOC_DAPM_SUPPLY("DAC Vref", ES8328_CHIPPOWER,
|
|
+ ES8328_CHIPPOWER_DACVREF_OFF, 1, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", ES8328_DACPOWER,
|
|
+ ES8328_DACPOWER_RDAC_OFF, 1),
|
|
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", ES8328_DACPOWER,
|
|
+ ES8328_DACPOWER_LDAC_OFF, 1),
|
|
+
|
|
+ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0,
|
|
+ &es8328_left_mixer_controls[0],
|
|
+ ARRAY_SIZE(es8328_left_mixer_controls)),
|
|
+ SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0,
|
|
+ &es8328_right_mixer_controls[0],
|
|
+ ARRAY_SIZE(es8328_right_mixer_controls)),
|
|
+
|
|
+ SND_SOC_DAPM_PGA("Right Out 2", ES8328_DACPOWER,
|
|
+ ES8328_DACPOWER_ROUT2_ON, 0, NULL, 0),
|
|
+ SND_SOC_DAPM_PGA("Left Out 2", ES8328_DACPOWER,
|
|
+ ES8328_DACPOWER_LOUT2_ON, 0, NULL, 0),
|
|
+ SND_SOC_DAPM_PGA("Right Out 1", ES8328_DACPOWER,
|
|
+ ES8328_DACPOWER_ROUT1_ON, 0, NULL, 0),
|
|
+ SND_SOC_DAPM_PGA("Left Out 1", ES8328_DACPOWER,
|
|
+ ES8328_DACPOWER_LOUT1_ON, 0, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
|
|
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
|
|
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
|
|
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
|
|
+
|
|
+ SND_SOC_DAPM_INPUT("LINPUT1"),
|
|
+ SND_SOC_DAPM_INPUT("LINPUT2"),
|
|
+ SND_SOC_DAPM_INPUT("RINPUT1"),
|
|
+ SND_SOC_DAPM_INPUT("RINPUT2"),
|
|
+};
|
|
+
|
|
+static const struct snd_soc_dapm_route es8328_dapm_routes[] = {
|
|
+
|
|
+ { "Left Line Mux", "Line 1", "LINPUT1" },
|
|
+ { "Left Line Mux", "Line 2", "LINPUT2" },
|
|
+ { "Left Line Mux", "PGA", "Left PGA Mux" },
|
|
+ { "Left Line Mux", "Differential", "Differential Mux" },
|
|
+
|
|
+ { "Right Line Mux", "Line 1", "RINPUT1" },
|
|
+ { "Right Line Mux", "Line 2", "RINPUT2" },
|
|
+ { "Right Line Mux", "PGA", "Right PGA Mux" },
|
|
+ { "Right Line Mux", "Differential", "Differential Mux" },
|
|
+
|
|
+ { "Left PGA Mux", "Line 1", "LINPUT1" },
|
|
+ { "Left PGA Mux", "Line 2", "LINPUT2" },
|
|
+ { "Left PGA Mux", "Differential", "Differential Mux" },
|
|
+
|
|
+ { "Right PGA Mux", "Line 1", "RINPUT1" },
|
|
+ { "Right PGA Mux", "Line 2", "RINPUT2" },
|
|
+ { "Right PGA Mux", "Differential", "Differential Mux" },
|
|
+
|
|
+ { "Differential Mux", "Line 1", "LINPUT1" },
|
|
+ { "Differential Mux", "Line 1", "RINPUT1" },
|
|
+ { "Differential Mux", "Line 2", "LINPUT2" },
|
|
+ { "Differential Mux", "Line 2", "RINPUT2" },
|
|
+
|
|
+ { "Left ADC Mux", "Stereo", "Left PGA Mux" },
|
|
+ { "Left ADC Mux", "Mono (Left)", "Left PGA Mux" },
|
|
+ { "Left ADC Mux", "Digital Mono", "Left PGA Mux" },
|
|
+
|
|
+ { "Right ADC Mux", "Stereo", "Right PGA Mux" },
|
|
+ { "Right ADC Mux", "Mono (Right)", "Right PGA Mux" },
|
|
+ { "Right ADC Mux", "Digital Mono", "Right PGA Mux" },
|
|
+
|
|
+ { "Left ADC", NULL, "Left ADC Mux" },
|
|
+ { "Right ADC", NULL, "Right ADC Mux" },
|
|
+
|
|
+ { "ADC DIG", NULL, "ADC STM" },
|
|
+ { "ADC DIG", NULL, "ADC Vref" },
|
|
+ { "ADC DIG", NULL, "ADC DLL" },
|
|
+
|
|
+ { "Left ADC", NULL, "ADC DIG" },
|
|
+ { "Right ADC", NULL, "ADC DIG" },
|
|
+
|
|
+ { "Mic Bias", NULL, "Mic Bias Gen" },
|
|
+
|
|
+ { "Left ADC", NULL, "Mic Bias" },
|
|
+ { "Right ADC", NULL, "Mic Bias" },
|
|
+
|
|
+ { "Left Line Mux", "Line 1", "LINPUT1" },
|
|
+ { "Left Line Mux", "Line 2", "LINPUT2" },
|
|
+ { "Left Line Mux", "PGA", "Left PGA Mux" },
|
|
+ { "Left Line Mux", "Differential", "Differential Mux" },
|
|
+
|
|
+ { "Right Line Mux", "Line 1", "RINPUT1" },
|
|
+ { "Right Line Mux", "Line 2", "RINPUT2" },
|
|
+ { "Right Line Mux", "PGA", "Right PGA Mux" },
|
|
+ { "Right Line Mux", "Differential", "Differential Mux" },
|
|
+
|
|
+ { "Left Out 1", NULL, "Left DAC" },
|
|
+ { "Right Out 1", NULL, "Right DAC" },
|
|
+ { "Left Out 2", NULL, "Left DAC" },
|
|
+ { "Right Out 2", NULL, "Right DAC" },
|
|
+
|
|
+ { "Left Mixer", "Playback Switch", "Left DAC" },
|
|
+ { "Left Mixer", "Left Bypass Switch", "Left Line Mux" },
|
|
+ { "Left Mixer", "Right Playback Switch", "Right DAC" },
|
|
+ { "Left Mixer", "Right Bypass Switch", "Right Line Mux" },
|
|
+
|
|
+ { "Right Mixer", "Left Playback Switch", "Left DAC" },
|
|
+ { "Right Mixer", "Left Bypass Switch", "Left Line Mux" },
|
|
+ { "Right Mixer", "Playback Switch", "Right DAC" },
|
|
+ { "Right Mixer", "Right Bypass Switch", "Right Line Mux" },
|
|
+
|
|
+ { "DAC DIG", NULL, "DAC STM" },
|
|
+ { "DAC DIG", NULL, "DAC Vref" },
|
|
+ { "DAC DIG", NULL, "DAC DLL" },
|
|
+
|
|
+ { "Left DAC", NULL, "DAC DIG" },
|
|
+ { "Right DAC", NULL, "DAC DIG" },
|
|
+
|
|
+ { "Left Out 1", NULL, "Left Mixer" },
|
|
+ { "LOUT1", NULL, "Left Out 1" },
|
|
+ { "Right Out 1", NULL, "Right Mixer" },
|
|
+ { "ROUT1", NULL, "Right Out 1" },
|
|
+
|
|
+ { "Left Out 2", NULL, "Left Mixer" },
|
|
+ { "LOUT2", NULL, "Left Out 2" },
|
|
+ { "Right Out 2", NULL, "Right Mixer" },
|
|
+ { "ROUT2", NULL, "Right Out 2" },
|
|
+};
|
|
+
|
|
+static int es8328_mute(struct snd_soc_dai *dai, int mute, int direction)
|
|
+{
|
|
+ return snd_soc_component_update_bits(dai->component, ES8328_DACCONTROL3,
|
|
+ ES8328_DACCONTROL3_DACMUTE,
|
|
+ mute ? ES8328_DACCONTROL3_DACMUTE : 0);
|
|
+}
|
|
+
|
|
+static int es8328_startup(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_component *component = dai->component;
|
|
+ struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ if (es8328->provider && es8328->sysclk_constraints)
|
|
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
|
|
+ SNDRV_PCM_HW_PARAM_RATE,
|
|
+ es8328->sysclk_constraints);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int es8328_hw_params(struct snd_pcm_substream *substream,
|
|
+ struct snd_pcm_hw_params *params,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_component *component = dai->component;
|
|
+ struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component);
|
|
+ int i;
|
|
+ int reg;
|
|
+ int wl;
|
|
+ int ratio;
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ reg = ES8328_DACCONTROL2;
|
|
+ else
|
|
+ reg = ES8328_ADCCONTROL5;
|
|
+
|
|
+ if (es8328->provider) {
|
|
+ if (!es8328->sysclk_constraints) {
|
|
+ dev_err(component->dev, "No MCLK configured\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < es8328->sysclk_constraints->count; i++)
|
|
+ if (es8328->sysclk_constraints->list[i] ==
|
|
+ params_rate(params))
|
|
+ break;
|
|
+
|
|
+ if (i == es8328->sysclk_constraints->count) {
|
|
+ dev_err(component->dev,
|
|
+ "LRCLK %d unsupported with current clock\n",
|
|
+ params_rate(params));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ ratio = es8328->mclk_ratios[i];
|
|
+ } else {
|
|
+ ratio = 0;
|
|
+ es8328->mclkdiv2 = 0;
|
|
+ }
|
|
+
|
|
+ snd_soc_component_update_bits(component, ES8328_MASTERMODE,
|
|
+ ES8328_MASTERMODE_MCLKDIV2,
|
|
+ es8328->mclkdiv2 ? ES8328_MASTERMODE_MCLKDIV2 : 0);
|
|
+
|
|
+ switch (params_width(params)) {
|
|
+ case 16:
|
|
+ wl = 3;
|
|
+ break;
|
|
+ case 18:
|
|
+ wl = 2;
|
|
+ break;
|
|
+ case 20:
|
|
+ wl = 1;
|
|
+ break;
|
|
+ case 24:
|
|
+ wl = 0;
|
|
+ break;
|
|
+ case 32:
|
|
+ wl = 4;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ snd_soc_component_update_bits(component, ES8328_DACCONTROL1,
|
|
+ ES8328_DACCONTROL1_DACWL_MASK,
|
|
+ wl << ES8328_DACCONTROL1_DACWL_SHIFT);
|
|
+
|
|
+ es8328->playback_fs = params_rate(params);
|
|
+ es8328_set_deemph(component);
|
|
+ } else
|
|
+ snd_soc_component_update_bits(component, ES8328_ADCCONTROL4,
|
|
+ ES8328_ADCCONTROL4_ADCWL_MASK,
|
|
+ wl << ES8328_ADCCONTROL4_ADCWL_SHIFT);
|
|
+
|
|
+ return snd_soc_component_update_bits(component, reg, ES8328_RATEMASK, ratio);
|
|
+}
|
|
+
|
|
+static int es8328_set_sysclk(struct snd_soc_dai *codec_dai,
|
|
+ int clk_id, unsigned int freq, int dir)
|
|
+{
|
|
+ struct snd_soc_component *component = codec_dai->component;
|
|
+ struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component);
|
|
+ int mclkdiv2 = 0;
|
|
+
|
|
+ switch (freq) {
|
|
+ case 0:
|
|
+ es8328->sysclk_constraints = NULL;
|
|
+ es8328->mclk_ratios = NULL;
|
|
+ break;
|
|
+ case 22579200:
|
|
+ mclkdiv2 = 1;
|
|
+ fallthrough;
|
|
+ case 11289600:
|
|
+ es8328->sysclk_constraints = &constraints_11289;
|
|
+ es8328->mclk_ratios = ratios_11289;
|
|
+ break;
|
|
+ case 24576000:
|
|
+ mclkdiv2 = 1;
|
|
+ fallthrough;
|
|
+ case 12288000:
|
|
+ es8328->sysclk_constraints = &constraints_12288;
|
|
+ es8328->mclk_ratios = ratios_12288;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ es8328->mclkdiv2 = mclkdiv2;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int es8328_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|
+ unsigned int fmt)
|
|
+{
|
|
+ struct snd_soc_component *component = codec_dai->component;
|
|
+ struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component);
|
|
+ u8 dac_mode = 0;
|
|
+ u8 adc_mode = 0;
|
|
+
|
|
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
|
|
+ case SND_SOC_DAIFMT_CBP_CFP:
|
|
+ /* Master serial port mode, with BCLK generated automatically */
|
|
+ snd_soc_component_update_bits(component, ES8328_MASTERMODE,
|
|
+ ES8328_MASTERMODE_MSC,
|
|
+ ES8328_MASTERMODE_MSC);
|
|
+ es8328->provider = true;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_CBC_CFC:
|
|
+ /* Slave serial port mode */
|
|
+ snd_soc_component_update_bits(component, ES8328_MASTERMODE,
|
|
+ ES8328_MASTERMODE_MSC, 0);
|
|
+ es8328->provider = false;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* interface format */
|
|
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
+ case SND_SOC_DAIFMT_I2S:
|
|
+ dac_mode |= ES8328_DACCONTROL1_DACFORMAT_I2S;
|
|
+ adc_mode |= ES8328_ADCCONTROL4_ADCFORMAT_I2S;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_RIGHT_J:
|
|
+ dac_mode |= ES8328_DACCONTROL1_DACFORMAT_RJUST;
|
|
+ adc_mode |= ES8328_ADCCONTROL4_ADCFORMAT_RJUST;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_LEFT_J:
|
|
+ dac_mode |= ES8328_DACCONTROL1_DACFORMAT_LJUST;
|
|
+ adc_mode |= ES8328_ADCCONTROL4_ADCFORMAT_LJUST;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* clock inversion */
|
|
+ if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF)
|
|
+ return -EINVAL;
|
|
+
|
|
+ snd_soc_component_update_bits(component, ES8328_DACCONTROL1,
|
|
+ ES8328_DACCONTROL1_DACFORMAT_MASK, dac_mode);
|
|
+ snd_soc_component_update_bits(component, ES8328_ADCCONTROL4,
|
|
+ ES8328_ADCCONTROL4_ADCFORMAT_MASK, adc_mode);
|
|
+ snd_soc_component_update_bits(component, ES8328_DACCONTROL21,
|
|
+ ES8328_DACCONTROL21_SLRCK, ES8328_DACCONTROL21_SLRCK);
|
|
+
|
|
+ /* Set Capture Digital Volume */
|
|
+ snd_soc_component_write(component, ES8328_ADCCONTROL8, 0);
|
|
+ snd_soc_component_write(component, ES8328_ADCCONTROL9, 0);
|
|
+
|
|
+ /* Set PCM Volume */
|
|
+ snd_soc_component_write(component, ES8328_LDACVOL, 0);
|
|
+ snd_soc_component_write(component, ES8328_RDACVOL, 0);
|
|
+
|
|
+ /* Set L/R Out Volume */
|
|
+ snd_soc_component_write(component, ES8328_LOUT1VOL, 0x1e);
|
|
+ snd_soc_component_write(component, ES8328_ROUT1VOL, 0x1e);
|
|
+ snd_soc_component_write(component, ES8328_LOUT2VOL, 0x1e);
|
|
+ snd_soc_component_write(component, ES8328_ROUT2VOL, 0x1e);
|
|
+
|
|
+ /* Set MIC PGA Volume */
|
|
+ snd_soc_component_write(component, ES8328_ADCCONTROL1, 0x88);
|
|
+
|
|
+ if (es8328->eswin_plat == 2) {
|
|
+ if (gpiod_get_value(es8328->front_jack_gpio) == 1 && gpiod_get_value(es8328->back_jack_gpio) == 0) {
|
|
+ /* Select default capture path ---> LIN1 */
|
|
+ snd_soc_component_write(component, ES8328_ADCCONTROL2, 0);
|
|
+ } else {
|
|
+ /* Select default capture path ---> LIN2 */
|
|
+ snd_soc_component_write(component, ES8328_ADCCONTROL2, 0x50);
|
|
+ }
|
|
+ } else {
|
|
+ /* Select default capture path ---> phone mic */
|
|
+ snd_soc_component_write(component, ES8328_ADCCONTROL2, 0xf0);
|
|
+ }
|
|
+
|
|
+ snd_soc_component_update_bits(component, ES8328_ADCCONTROL3,
|
|
+ ES8328_ADCCONTROL3_DS, 0);
|
|
+
|
|
+ /* Select Playback path */
|
|
+ snd_soc_component_update_bits(component, ES8328_DACCONTROL17,
|
|
+ ES8328_DACCONTROL17_LD2LO, ES8328_DACCONTROL17_LD2LO);
|
|
+ snd_soc_component_update_bits(component, ES8328_DACCONTROL20,
|
|
+ ES8328_DACCONTROL20_RD2RO, ES8328_DACCONTROL20_RD2RO);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int es8328_set_bias_level(struct snd_soc_component *component,
|
|
+ enum snd_soc_bias_level level)
|
|
+{
|
|
+ switch (level) {
|
|
+ case SND_SOC_BIAS_ON:
|
|
+ break;
|
|
+
|
|
+ case SND_SOC_BIAS_PREPARE:
|
|
+ /* VREF, VMID=2x50k, digital enabled */
|
|
+ snd_soc_component_write(component, ES8328_CHIPPOWER, 0);
|
|
+ snd_soc_component_update_bits(component, ES8328_CONTROL1,
|
|
+ ES8328_CONTROL1_VMIDSEL_MASK |
|
|
+ ES8328_CONTROL1_ENREF,
|
|
+ ES8328_CONTROL1_VMIDSEL_50k |
|
|
+ ES8328_CONTROL1_ENREF);
|
|
+ break;
|
|
+
|
|
+ case SND_SOC_BIAS_STANDBY:
|
|
+ if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) {
|
|
+ snd_soc_component_update_bits(component, ES8328_CONTROL1,
|
|
+ ES8328_CONTROL1_VMIDSEL_MASK |
|
|
+ ES8328_CONTROL1_ENREF,
|
|
+ ES8328_CONTROL1_VMIDSEL_5k |
|
|
+ ES8328_CONTROL1_ENREF);
|
|
+
|
|
+ /* Charge caps */
|
|
+ msleep(100);
|
|
+ }
|
|
+
|
|
+ snd_soc_component_write(component, ES8328_CONTROL2,
|
|
+ ES8328_CONTROL2_OVERCURRENT_ON |
|
|
+ ES8328_CONTROL2_THERMAL_SHUTDOWN_ON);
|
|
+
|
|
+ /* VREF, VMID=2*500k, digital stopped */
|
|
+ snd_soc_component_update_bits(component, ES8328_CONTROL1,
|
|
+ ES8328_CONTROL1_VMIDSEL_MASK |
|
|
+ ES8328_CONTROL1_ENREF,
|
|
+ ES8328_CONTROL1_VMIDSEL_500k |
|
|
+ ES8328_CONTROL1_ENREF);
|
|
+ break;
|
|
+
|
|
+ case SND_SOC_BIAS_OFF:
|
|
+ snd_soc_component_update_bits(component, ES8328_CONTROL1,
|
|
+ ES8328_CONTROL1_VMIDSEL_MASK |
|
|
+ ES8328_CONTROL1_ENREF,
|
|
+ 0);
|
|
+ break;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_soc_dai_ops es8328_dai_ops = {
|
|
+ .startup = es8328_startup,
|
|
+ .hw_params = es8328_hw_params,
|
|
+ .mute_stream = es8328_mute,
|
|
+ .set_sysclk = es8328_set_sysclk,
|
|
+ .set_fmt = es8328_set_dai_fmt,
|
|
+ .no_capture_mute = 1,
|
|
+};
|
|
+
|
|
+static struct snd_soc_dai_driver es8328_dai[3] = {
|
|
+ {
|
|
+ .name = "es8328-0-hifi-analog",
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ES8328_RATES,
|
|
+ .formats = ES8328_FORMATS,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ES8328_RATES,
|
|
+ .formats = ES8328_FORMATS,
|
|
+ },
|
|
+ .ops = &es8328_dai_ops,
|
|
+ .symmetric_rate = 1,
|
|
+ },
|
|
+ {
|
|
+ .name = "es8328-1-hifi-analog",
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ES8328_RATES,
|
|
+ .formats = ES8328_FORMATS,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ES8328_RATES,
|
|
+ .formats = ES8328_FORMATS,
|
|
+ },
|
|
+ .ops = &es8328_dai_ops,
|
|
+ .symmetric_rate = 1,
|
|
+ },
|
|
+ {
|
|
+ .name = "es8328-2-hifi-analog",
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ES8328_RATES,
|
|
+ .formats = ES8328_FORMATS,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ES8328_RATES,
|
|
+ .formats = ES8328_FORMATS,
|
|
+ },
|
|
+ .ops = &es8328_dai_ops,
|
|
+ .symmetric_rate = 1,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int es8328_suspend(struct snd_soc_component *component)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int es8328_resume(struct snd_soc_component *component)
|
|
+{
|
|
+ struct regmap *regmap = dev_get_regmap(component->dev, NULL);
|
|
+ int ret;
|
|
+
|
|
+ regcache_mark_dirty(regmap);
|
|
+ ret = regcache_sync(regmap);
|
|
+ if (ret) {
|
|
+ dev_err(component->dev, "unable to sync regcache\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int es8328_component_probe(struct snd_soc_component *component)
|
|
+{
|
|
+ struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ es8328->component = component;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void es8328_remove(struct snd_soc_component *component)
|
|
+{
|
|
+}
|
|
+
|
|
+const struct regmap_config es8328_regmap_config = {
|
|
+ .reg_bits = 8,
|
|
+ .val_bits = 8,
|
|
+ .max_register = ES8328_REG_MAX,
|
|
+ .cache_type = REGCACHE_MAPLE,
|
|
+ .use_single_read = true,
|
|
+ .use_single_write = true,
|
|
+};
|
|
+EXPORT_SYMBOL_GPL(es8328_regmap_config);
|
|
+
|
|
+static const struct snd_soc_component_driver es8328_component_driver = {
|
|
+ .probe = es8328_component_probe,
|
|
+ .remove = es8328_remove,
|
|
+ .suspend = es8328_suspend,
|
|
+ .resume = es8328_resume,
|
|
+ .set_bias_level = es8328_set_bias_level,
|
|
+ .controls = es8328_snd_controls,
|
|
+ .num_controls = ARRAY_SIZE(es8328_snd_controls),
|
|
+ .dapm_widgets = es8328_dapm_widgets,
|
|
+ .num_dapm_widgets = ARRAY_SIZE(es8328_dapm_widgets),
|
|
+ .dapm_routes = es8328_dapm_routes,
|
|
+ .num_dapm_routes = ARRAY_SIZE(es8328_dapm_routes),
|
|
+ .suspend_bias_off = 1,
|
|
+ .idle_bias_on = 1,
|
|
+ .use_pmdown_time = 1,
|
|
+ .endianness = 1,
|
|
+};
|
|
+
|
|
+static irqreturn_t es8328_jack_irq(int irq, void *data)
|
|
+{
|
|
+ struct es8328_priv *es8328 = data;
|
|
+ struct snd_soc_component *comp = es8328->component;
|
|
+ int front_jack_value, back_jack_value;
|
|
+
|
|
+ if (!es8328->front_jack_gpio || !es8328->back_jack_gpio) {
|
|
+ dev_warn(comp->dev, "jack gpio desc is null\n");
|
|
+ return IRQ_NONE;
|
|
+ }
|
|
+
|
|
+ front_jack_value = gpiod_get_value(es8328->front_jack_gpio);
|
|
+ back_jack_value = gpiod_get_value(es8328->back_jack_gpio);
|
|
+
|
|
+ dev_dbg(comp->dev, "front jack value:%d, back jack value:%d\n", front_jack_value, back_jack_value);
|
|
+
|
|
+ if (back_jack_value == 0 && front_jack_value == 1) {
|
|
+ /* Select Capture path ---> LIN1 */
|
|
+ regmap_write(comp->regmap, ES8328_ADCCONTROL2, 0);
|
|
+ } else {
|
|
+ /* Select Capture path ---> LIN2 */
|
|
+ regmap_write(comp->regmap, ES8328_ADCCONTROL2, 0x50);
|
|
+ }
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+int es8328_probe(struct device *dev, struct regmap *regmap)
|
|
+{
|
|
+ struct es8328_priv *es8328;
|
|
+ int ret;
|
|
+
|
|
+ if (IS_ERR(regmap))
|
|
+ return PTR_ERR(regmap);
|
|
+
|
|
+ es8328 = devm_kzalloc(dev, sizeof(*es8328), GFP_KERNEL);
|
|
+ if (es8328 == NULL)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ es8328->regmap = regmap;
|
|
+
|
|
+ dev_set_drvdata(dev, es8328);
|
|
+
|
|
+ ret = device_property_read_u32(dev, "eswin-plat", &es8328->eswin_plat);
|
|
+ if (0 != ret) {
|
|
+ es8328->eswin_plat = 0;
|
|
+ }
|
|
+ dev_info(dev, "eswin platform:%d\n", es8328->eswin_plat);
|
|
+
|
|
+ if (es8328->eswin_plat == 2) {
|
|
+ es8328->front_jack_gpio = devm_gpiod_get(dev, "front-jack", GPIOD_IN);
|
|
+ ret = IS_ERR(es8328->front_jack_gpio);
|
|
+ if(ret) {
|
|
+ dev_err(dev, "can not get front jack gpio\n");
|
|
+ }
|
|
+
|
|
+ es8328->back_jack_gpio = devm_gpiod_get(dev, "back-jack", GPIOD_IN);
|
|
+ ret = IS_ERR(es8328->back_jack_gpio);
|
|
+ if(ret) {
|
|
+ dev_err(dev, "can not get back jack gpio\n");
|
|
+ }
|
|
+
|
|
+ ret = devm_request_threaded_irq(dev, gpiod_to_irq(es8328->front_jack_gpio), NULL, es8328_jack_irq,
|
|
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
+ "front jack", es8328);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Failed to request front irq[%d], ret:%d\n", gpiod_to_irq(es8328->back_jack_gpio), ret);
|
|
+ }
|
|
+
|
|
+ ret = devm_request_threaded_irq(dev, gpiod_to_irq(es8328->back_jack_gpio), NULL, es8328_jack_irq,
|
|
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
+ "back jack", es8328);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Failed to request back irq[%d], ret:%d\n", gpiod_to_irq(es8328->back_jack_gpio), ret);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (of_node_name_prefix(dev->of_node, "es8388-0")) {
|
|
+ ret = devm_snd_soc_register_component(dev,
|
|
+ &es8328_component_driver, &es8328_dai[0], 1);
|
|
+ } else if (of_node_name_prefix(dev->of_node, "es8388-1")) {
|
|
+ ret = devm_snd_soc_register_component(dev,
|
|
+ &es8328_component_driver, &es8328_dai[1], 1);
|
|
+ } else {
|
|
+ ret = devm_snd_soc_register_component(dev,
|
|
+ &es8328_component_driver, &es8328_dai[2], 1);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(es8328_probe);
|
|
+
|
|
+MODULE_DESCRIPTION("ASoC ES8328 driver");
|
|
+MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/sound/soc/codecs/eswin/es8328.h b/sound/soc/codecs/eswin/es8328.h
|
|
new file mode 100644
|
|
index 000000000000..70515466a4b2
|
|
--- /dev/null
|
|
+++ b/sound/soc/codecs/eswin/es8328.h
|
|
@@ -0,0 +1,299 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * es8328.h -- ES8328 ALSA SoC Audio driver
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * Copyright (C) 2021 ESWIN, Inc. All rights reserved.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
+ * more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef _ES8328_H
|
|
+#define _ES8328_H
|
|
+
|
|
+#include <linux/regmap.h>
|
|
+
|
|
+struct device;
|
|
+
|
|
+extern const struct regmap_config es8328_regmap_config;
|
|
+int es8328_probe(struct device *dev, struct regmap *regmap);
|
|
+
|
|
+#define ES8328_DACLVOL 46
|
|
+#define ES8328_DACRVOL 47
|
|
+#define ES8328_DACCTL 28
|
|
+#define ES8328_RATEMASK (0x1f << 0)
|
|
+
|
|
+#define ES8328_CONTROL1 0x00
|
|
+#define ES8328_CONTROL1_VMIDSEL_OFF (0 << 0)
|
|
+#define ES8328_CONTROL1_VMIDSEL_50k (1 << 0)
|
|
+#define ES8328_CONTROL1_VMIDSEL_500k (2 << 0)
|
|
+#define ES8328_CONTROL1_VMIDSEL_5k (3 << 0)
|
|
+#define ES8328_CONTROL1_VMIDSEL_MASK (3 << 0)
|
|
+#define ES8328_CONTROL1_ENREF (1 << 2)
|
|
+#define ES8328_CONTROL1_SEQEN (1 << 3)
|
|
+#define ES8328_CONTROL1_SAMEFS (1 << 4)
|
|
+#define ES8328_CONTROL1_DACMCLK_ADC (0 << 5)
|
|
+#define ES8328_CONTROL1_DACMCLK_DAC (1 << 5)
|
|
+#define ES8328_CONTROL1_LRCM (1 << 6)
|
|
+#define ES8328_CONTROL1_SCP_RESET (1 << 7)
|
|
+
|
|
+#define ES8328_CONTROL2 0x01
|
|
+#define ES8328_CONTROL2_VREF_BUF_OFF (1 << 0)
|
|
+#define ES8328_CONTROL2_VREF_LOWPOWER (1 << 1)
|
|
+#define ES8328_CONTROL2_IBIASGEN_OFF (1 << 2)
|
|
+#define ES8328_CONTROL2_ANALOG_OFF (1 << 3)
|
|
+#define ES8328_CONTROL2_VREF_BUF_LOWPOWER (1 << 4)
|
|
+#define ES8328_CONTROL2_VCM_MOD_LOWPOWER (1 << 5)
|
|
+#define ES8328_CONTROL2_OVERCURRENT_ON (1 << 6)
|
|
+#define ES8328_CONTROL2_THERMAL_SHUTDOWN_ON (1 << 7)
|
|
+
|
|
+#define ES8328_CHIPPOWER 0x02
|
|
+#define ES8328_CHIPPOWER_DACVREF_OFF 0
|
|
+#define ES8328_CHIPPOWER_ADCVREF_OFF 1
|
|
+#define ES8328_CHIPPOWER_DACDLL_OFF 2
|
|
+#define ES8328_CHIPPOWER_ADCDLL_OFF 3
|
|
+#define ES8328_CHIPPOWER_DACSTM_RESET 4
|
|
+#define ES8328_CHIPPOWER_ADCSTM_RESET 5
|
|
+#define ES8328_CHIPPOWER_DACDIG_OFF 6
|
|
+#define ES8328_CHIPPOWER_ADCDIG_OFF 7
|
|
+
|
|
+#define ES8328_ADCPOWER 0x03
|
|
+#define ES8328_ADCPOWER_INT1_LOWPOWER 0
|
|
+#define ES8328_ADCPOWER_FLASH_ADC_LOWPOWER 1
|
|
+#define ES8328_ADCPOWER_ADC_BIAS_GEN_OFF 2
|
|
+#define ES8328_ADCPOWER_MIC_BIAS_OFF 3
|
|
+#define ES8328_ADCPOWER_ADCR_OFF 4
|
|
+#define ES8328_ADCPOWER_ADCL_OFF 5
|
|
+#define ES8328_ADCPOWER_AINR_OFF 6
|
|
+#define ES8328_ADCPOWER_AINL_OFF 7
|
|
+
|
|
+#define ES8328_DACPOWER 0x04
|
|
+#define ES8328_DACPOWER_OUT3_ON 0
|
|
+#define ES8328_DACPOWER_MONO_ON 1
|
|
+#define ES8328_DACPOWER_ROUT2_ON 2
|
|
+#define ES8328_DACPOWER_LOUT2_ON 3
|
|
+#define ES8328_DACPOWER_ROUT1_ON 4
|
|
+#define ES8328_DACPOWER_LOUT1_ON 5
|
|
+#define ES8328_DACPOWER_RDAC_OFF 6
|
|
+#define ES8328_DACPOWER_LDAC_OFF 7
|
|
+
|
|
+#define ES8328_CHIPLOPOW1 0x05
|
|
+#define ES8328_CHIPLOPOW2 0x06
|
|
+#define ES8328_ANAVOLMANAG 0x07
|
|
+
|
|
+#define ES8328_MASTERMODE 0x08
|
|
+#define ES8328_MASTERMODE_BCLKDIV (0 << 0)
|
|
+#define ES8328_MASTERMODE_BCLK_INV (1 << 5)
|
|
+#define ES8328_MASTERMODE_MCLKDIV2 (1 << 6)
|
|
+#define ES8328_MASTERMODE_MSC (1 << 7)
|
|
+
|
|
+#define ES8328_ADCCONTROL1 0x09
|
|
+#define ES8328_ADCCONTROL2 0x0a
|
|
+#define ES8328_ADCCONTROL3 0x0b
|
|
+#define ES8328_ADCCONTROL3_DS (1 << 7)
|
|
+
|
|
+#define ES8328_ADCCONTROL4 0x0c
|
|
+#define ES8328_ADCCONTROL4_ADCFORMAT_MASK (3 << 0)
|
|
+#define ES8328_ADCCONTROL4_ADCFORMAT_I2S (0 << 0)
|
|
+#define ES8328_ADCCONTROL4_ADCFORMAT_LJUST (1 << 0)
|
|
+#define ES8328_ADCCONTROL4_ADCFORMAT_RJUST (2 << 0)
|
|
+#define ES8328_ADCCONTROL4_ADCFORMAT_PCM (3 << 0)
|
|
+#define ES8328_ADCCONTROL4_ADCWL_SHIFT 2
|
|
+#define ES8328_ADCCONTROL4_ADCWL_MASK (7 << 2)
|
|
+#define ES8328_ADCCONTROL4_ADCLRP_I2S_POL_NORMAL (0 << 5)
|
|
+#define ES8328_ADCCONTROL4_ADCLRP_I2S_POL_INV (1 << 5)
|
|
+#define ES8328_ADCCONTROL4_ADCLRP_PCM_MSB_CLK2 (0 << 5)
|
|
+#define ES8328_ADCCONTROL4_ADCLRP_PCM_MSB_CLK1 (1 << 5)
|
|
+
|
|
+#define ES8328_ADCCONTROL5 0x0d
|
|
+#define ES8328_ADCCONTROL5_RATEMASK (0x1f << 0)
|
|
+
|
|
+#define ES8328_ADCCONTROL6 0x0e
|
|
+
|
|
+#define ES8328_ADCCONTROL7 0x0f
|
|
+#define ES8328_ADCCONTROL7_ADC_MUTE (1 << 2)
|
|
+#define ES8328_ADCCONTROL7_ADC_LER (1 << 3)
|
|
+#define ES8328_ADCCONTROL7_ADC_ZERO_CROSS (1 << 4)
|
|
+#define ES8328_ADCCONTROL7_ADC_SOFT_RAMP (1 << 5)
|
|
+#define ES8328_ADCCONTROL7_ADC_RAMP_RATE_4 (0 << 6)
|
|
+#define ES8328_ADCCONTROL7_ADC_RAMP_RATE_8 (1 << 6)
|
|
+#define ES8328_ADCCONTROL7_ADC_RAMP_RATE_16 (2 << 6)
|
|
+#define ES8328_ADCCONTROL7_ADC_RAMP_RATE_32 (3 << 6)
|
|
+
|
|
+#define ES8328_ADCCONTROL8 0x10
|
|
+#define ES8328_ADCCONTROL9 0x11
|
|
+#define ES8328_ADCCONTROL10 0x12
|
|
+#define ES8328_ADCCONTROL11 0x13
|
|
+#define ES8328_ADCCONTROL12 0x14
|
|
+#define ES8328_ADCCONTROL13 0x15
|
|
+#define ES8328_ADCCONTROL14 0x16
|
|
+
|
|
+#define ES8328_DACCONTROL1 0x17
|
|
+#define ES8328_DACCONTROL1_DACFORMAT_MASK (3 << 1)
|
|
+#define ES8328_DACCONTROL1_DACFORMAT_I2S (0 << 1)
|
|
+#define ES8328_DACCONTROL1_DACFORMAT_LJUST (1 << 1)
|
|
+#define ES8328_DACCONTROL1_DACFORMAT_RJUST (2 << 1)
|
|
+#define ES8328_DACCONTROL1_DACFORMAT_PCM (3 << 1)
|
|
+#define ES8328_DACCONTROL1_DACWL_SHIFT 3
|
|
+#define ES8328_DACCONTROL1_DACWL_MASK (7 << 3)
|
|
+#define ES8328_DACCONTROL1_DACLRP_I2S_POL_NORMAL (0 << 6)
|
|
+#define ES8328_DACCONTROL1_DACLRP_I2S_POL_INV (1 << 6)
|
|
+#define ES8328_DACCONTROL1_DACLRP_PCM_MSB_CLK2 (0 << 6)
|
|
+#define ES8328_DACCONTROL1_DACLRP_PCM_MSB_CLK1 (1 << 6)
|
|
+#define ES8328_DACCONTROL1_LRSWAP (1 << 7)
|
|
+
|
|
+#define ES8328_DACCONTROL2 0x18
|
|
+#define ES8328_DACCONTROL2_RATEMASK (0x1f << 0)
|
|
+#define ES8328_DACCONTROL2_DOUBLESPEED (1 << 5)
|
|
+
|
|
+#define ES8328_DACCONTROL3 0x19
|
|
+#define ES8328_DACCONTROL3_AUTOMUTE (1 << 2)
|
|
+#define ES8328_DACCONTROL3_DACMUTE (1 << 2)
|
|
+#define ES8328_DACCONTROL3_LEFTGAINVOL (1 << 3)
|
|
+#define ES8328_DACCONTROL3_DACZEROCROSS (1 << 4)
|
|
+#define ES8328_DACCONTROL3_DACSOFTRAMP (1 << 5)
|
|
+#define ES8328_DACCONTROL3_DACRAMPRATE (3 << 6)
|
|
+
|
|
+#define ES8328_LDACVOL 0x1a
|
|
+#define ES8328_LDACVOL_MASK (0 << 0)
|
|
+#define ES8328_LDACVOL_MAX (0xc0)
|
|
+
|
|
+#define ES8328_RDACVOL 0x1b
|
|
+#define ES8328_RDACVOL_MASK (0 << 0)
|
|
+#define ES8328_RDACVOL_MAX (0xc0)
|
|
+
|
|
+#define ES8328_DACCONTROL6 0x1c
|
|
+#define ES8328_DACCONTROL6_CLICKFREE (1 << 3)
|
|
+#define ES8328_DACCONTROL6_DAC_INVR (1 << 4)
|
|
+#define ES8328_DACCONTROL6_DAC_INVL (1 << 5)
|
|
+#define ES8328_DACCONTROL6_DEEMPH_MASK (3 << 6)
|
|
+#define ES8328_DACCONTROL6_DEEMPH_OFF (0 << 6)
|
|
+#define ES8328_DACCONTROL6_DEEMPH_32k (1 << 6)
|
|
+#define ES8328_DACCONTROL6_DEEMPH_44_1k (2 << 6)
|
|
+#define ES8328_DACCONTROL6_DEEMPH_48k (3 << 6)
|
|
+
|
|
+#define ES8328_DACCONTROL7 0x1d
|
|
+#define ES8328_DACCONTROL7_VPP_SCALE_3p5 (0 << 0)
|
|
+#define ES8328_DACCONTROL7_VPP_SCALE_4p0 (1 << 0)
|
|
+#define ES8328_DACCONTROL7_VPP_SCALE_3p0 (2 << 0)
|
|
+#define ES8328_DACCONTROL7_VPP_SCALE_2p5 (3 << 0)
|
|
+#define ES8328_DACCONTROL7_SHELVING_STRENGTH (1 << 2) /* In eights */
|
|
+#define ES8328_DACCONTROL7_MONO (1 << 5)
|
|
+#define ES8328_DACCONTROL7_ZEROR (1 << 6)
|
|
+#define ES8328_DACCONTROL7_ZEROL (1 << 7)
|
|
+
|
|
+/* Shelving filter */
|
|
+#define ES8328_DACCONTROL8 0x1e
|
|
+#define ES8328_DACCONTROL9 0x1f
|
|
+#define ES8328_DACCONTROL10 0x20
|
|
+#define ES8328_DACCONTROL11 0x21
|
|
+#define ES8328_DACCONTROL12 0x22
|
|
+#define ES8328_DACCONTROL13 0x23
|
|
+#define ES8328_DACCONTROL14 0x24
|
|
+#define ES8328_DACCONTROL15 0x25
|
|
+
|
|
+#define ES8328_DACCONTROL16 0x26
|
|
+#define ES8328_DACCONTROL16_RMIXSEL_RIN1 (0 << 0)
|
|
+#define ES8328_DACCONTROL16_RMIXSEL_RIN2 (1 << 0)
|
|
+#define ES8328_DACCONTROL16_RMIXSEL_RIN3 (2 << 0)
|
|
+#define ES8328_DACCONTROL16_RMIXSEL_RADC (3 << 0)
|
|
+#define ES8328_DACCONTROL16_LMIXSEL_LIN1 (0 << 3)
|
|
+#define ES8328_DACCONTROL16_LMIXSEL_LIN2 (1 << 3)
|
|
+#define ES8328_DACCONTROL16_LMIXSEL_LIN3 (2 << 3)
|
|
+#define ES8328_DACCONTROL16_LMIXSEL_LADC (3 << 3)
|
|
+
|
|
+#define ES8328_DACCONTROL17 0x27
|
|
+#define ES8328_DACCONTROL17_LI2LOVOL (7 << 3)
|
|
+#define ES8328_DACCONTROL17_LI2LO (1 << 6)
|
|
+#define ES8328_DACCONTROL17_LD2LO (1 << 7)
|
|
+
|
|
+#define ES8328_DACCONTROL18 0x28
|
|
+#define ES8328_DACCONTROL18_RI2LOVOL (7 << 3)
|
|
+#define ES8328_DACCONTROL18_RI2LO (1 << 6)
|
|
+#define ES8328_DACCONTROL18_RD2LO (1 << 7)
|
|
+
|
|
+#define ES8328_DACCONTROL19 0x29
|
|
+#define ES8328_DACCONTROL19_LI2ROVOL (7 << 3)
|
|
+#define ES8328_DACCONTROL19_LI2RO (1 << 6)
|
|
+#define ES8328_DACCONTROL19_LD2RO (1 << 7)
|
|
+
|
|
+#define ES8328_DACCONTROL20 0x2a
|
|
+#define ES8328_DACCONTROL20_RI2ROVOL (7 << 3)
|
|
+#define ES8328_DACCONTROL20_RI2RO (1 << 6)
|
|
+#define ES8328_DACCONTROL20_RD2RO (1 << 7)
|
|
+
|
|
+#define ES8328_DACCONTROL21 0x2b
|
|
+#define ES8328_DACCONTROL21_SLRCK (1 << 7)
|
|
+
|
|
+#define ES8328_DACCONTROL22 0x2c
|
|
+#define ES8328_DACCONTROL22_RI2MOVOL (7 << 3)
|
|
+#define ES8328_DACCONTROL22_RI2MO (1 << 6)
|
|
+#define ES8328_DACCONTROL22_RD2MO (1 << 7)
|
|
+
|
|
+#define ES8328_DACCONTROL23 0x2d
|
|
+#define ES8328_DACCONTROL23_MOUTINV (1 << 1)
|
|
+#define ES8328_DACCONTROL23_HPSWPOL (1 << 2)
|
|
+#define ES8328_DACCONTROL23_HPSWEN (1 << 3)
|
|
+#define ES8328_DACCONTROL23_VROI_1p5k (0 << 4)
|
|
+#define ES8328_DACCONTROL23_VROI_40k (1 << 4)
|
|
+#define ES8328_DACCONTROL23_OUT3_VREF (0 << 5)
|
|
+#define ES8328_DACCONTROL23_OUT3_ROUT1 (1 << 5)
|
|
+#define ES8328_DACCONTROL23_OUT3_MONOOUT (2 << 5)
|
|
+#define ES8328_DACCONTROL23_OUT3_RIGHT_MIXER (3 << 5)
|
|
+#define ES8328_DACCONTROL23_ROUT2INV (1 << 7)
|
|
+
|
|
+/* LOUT1 Amplifier */
|
|
+#define ES8328_LOUT1VOL 0x2e
|
|
+#define ES8328_LOUT1VOL_MASK (0 << 5)
|
|
+#define ES8328_LOUT1VOL_MAX (0x24)
|
|
+
|
|
+/* ROUT1 Amplifier */
|
|
+#define ES8328_ROUT1VOL 0x2f
|
|
+#define ES8328_ROUT1VOL_MASK (0 << 5)
|
|
+#define ES8328_ROUT1VOL_MAX (0x24)
|
|
+
|
|
+#define ES8328_OUT1VOL_MAX (0x24)
|
|
+
|
|
+/* LOUT2 Amplifier */
|
|
+#define ES8328_LOUT2VOL 0x30
|
|
+#define ES8328_LOUT2VOL_MASK (0 << 5)
|
|
+#define ES8328_LOUT2VOL_MAX (0x24)
|
|
+
|
|
+/* ROUT2 Amplifier */
|
|
+#define ES8328_ROUT2VOL 0x31
|
|
+#define ES8328_ROUT2VOL_MASK (0 << 5)
|
|
+#define ES8328_ROUT2VOL_MAX (0x24)
|
|
+
|
|
+#define ES8328_OUT2VOL_MAX (0x24)
|
|
+
|
|
+/* Mono Out Amplifier */
|
|
+#define ES8328_MONOOUTVOL 0x32
|
|
+#define ES8328_MONOOUTVOL_MASK (0 << 5)
|
|
+#define ES8328_MONOOUTVOL_MAX (0x24)
|
|
+
|
|
+#define ES8328_DACCONTROL29 0x33
|
|
+#define ES8328_DACCONTROL30 0x34
|
|
+
|
|
+#define ES8328_SYSCLK 0
|
|
+
|
|
+#define ES8328_REG_MAX 0x35
|
|
+
|
|
+#define ES8328_1536FS 1536
|
|
+#define ES8328_1024FS 1024
|
|
+#define ES8328_768FS 768
|
|
+#define ES8328_512FS 512
|
|
+#define ES8328_384FS 384
|
|
+#define ES8328_256FS 256
|
|
+#define ES8328_128FS 128
|
|
+
|
|
+#endif
|
|
diff --git a/sound/soc/eswin/Kconfig b/sound/soc/eswin/Kconfig
|
|
new file mode 100644
|
|
index 000000000000..7c930a329daa
|
|
--- /dev/null
|
|
+++ b/sound/soc/eswin/Kconfig
|
|
@@ -0,0 +1,11 @@
|
|
+menu "SND ESWIN SOC"
|
|
+
|
|
+config SND_ESWIN_DW_I2S
|
|
+ tristate "Eswin Dw I2S Device Driver"
|
|
+ select SND_SOC_GENERIC_DMAENGINE_PCM
|
|
+ help
|
|
+ Say Y or M if you want to add support for I2S driver for
|
|
+ Eswin dw I2S device. The device supports up to
|
|
+ a maximum of 8 channels each for play and record.
|
|
+
|
|
+endmenu
|
|
diff --git a/sound/soc/eswin/Makefile b/sound/soc/eswin/Makefile
|
|
new file mode 100644
|
|
index 000000000000..2c133c695dbe
|
|
--- /dev/null
|
|
+++ b/sound/soc/eswin/Makefile
|
|
@@ -0,0 +1,5 @@
|
|
+# SPDX-License-Identifier: GPL-2.0-only
|
|
+# ESWIN Platform Support
|
|
+snd-soc-i2s-objs := esw-i2s.o esw-audio-proc.o
|
|
+
|
|
+obj-$(CONFIG_SND_ESWIN_DW_I2S) += snd-soc-i2s.o
|
|
\ No newline at end of file
|
|
diff --git a/sound/soc/eswin/esw-audio-proc.c b/sound/soc/eswin/esw-audio-proc.c
|
|
new file mode 100644
|
|
index 000000000000..5ffed4c4ab34
|
|
--- /dev/null
|
|
+++ b/sound/soc/eswin/esw-audio-proc.c
|
|
@@ -0,0 +1,488 @@
|
|
+/*
|
|
+ *
|
|
+ * Copyright (C) 2021 ESWIN, Inc. All rights reserved.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
+ * more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/proc_fs.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/seq_file.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/dma-mapping.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/eventfd.h>
|
|
+#include <linux/ioctl.h>
|
|
+#include <linux/device.h>
|
|
+
|
|
+// proc data definition
|
|
+typedef enum
|
|
+{
|
|
+ ADEC_CREATE_CHN,
|
|
+ ADEC_SEND_STREAM,
|
|
+ ADEC_GET_FRAME,
|
|
+ ADEC_PARSE_PACKET,
|
|
+ ADEC_DECODE_STREAM,
|
|
+ AENC_CREATE_CHN,
|
|
+ AENC_SEND_FRAME,
|
|
+ AENC_GET_STREAM,
|
|
+ AENC_ENCODE_FRAME,
|
|
+ AGC_PROCESS,
|
|
+ ANS_PROCESS,
|
|
+ AEC_PROCESS,
|
|
+ DRC_PROCESS,
|
|
+ EQ_PROCESS,
|
|
+ DCBLOCK_PROCESS,
|
|
+ VOLUME_PROCESS,
|
|
+ SRC_HOST_PROCESS,
|
|
+ SRC_DAI_PROCESS,
|
|
+ HOST_PROCESS,
|
|
+ DAI_PROCESS,
|
|
+ AO_START,
|
|
+ AO_PROCESS_FRAME,
|
|
+ AO_WRITE_FRAME,
|
|
+ AO_STOP,
|
|
+ AI_START,
|
|
+ AI_READ_FRAME,
|
|
+ AI_PROCESS_FRAME,
|
|
+ AI_STOP,
|
|
+} PERF_MARK;
|
|
+
|
|
+static struct proc_dir_entry *proc_esaudio;
|
|
+static int g_switch = 0;
|
|
+
|
|
+// Device data definition
|
|
+#define MAX_PERF_SIZE 1024
|
|
+enum DEVICES_ID{
|
|
+ INVALID_DEVICE = -1,
|
|
+ AO = 0,
|
|
+ AI,
|
|
+ AENC,
|
|
+ ADEC,
|
|
+ NUM_DEVICES,
|
|
+};
|
|
+static const char *device_names[NUM_DEVICES] = {"ao", "ai", "aenc", "adec"};
|
|
+static int audio_proc_major[NUM_DEVICES] = {0};
|
|
+static struct class *audio_proc_class = NULL;
|
|
+static struct device *audio_proc_device[NUM_DEVICES] = {NULL};
|
|
+static int32_t *g_perf_data[NUM_DEVICES] = {NULL};
|
|
+
|
|
+static void show_aenc_data(struct seq_file *m)
|
|
+{
|
|
+ seq_printf(m,"----------------------------------------------------AENC PERF STATISTIC BEGIN"
|
|
+ "----------------------------------------------------\n");
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m, "------------------------------------------------------------------------"
|
|
+ "----------------------------------------------------------\n");
|
|
+ seq_printf(m, "audio encoder performance(us):\n");
|
|
+ seq_printf(m, "%-14s%-14s%-14s%-14s\n", "create_chn", "send_frame", "get_stream", "encode_frame");
|
|
+ seq_printf(m, "----------------------------------------------------------------------------------"
|
|
+ "------------------------------------------------\n");
|
|
+ seq_printf(m, "%-14d%-14d%-14d%-14d\n", g_perf_data[AENC][AENC_CREATE_CHN], g_perf_data[AENC][AENC_SEND_FRAME],
|
|
+ g_perf_data[AENC][AENC_GET_STREAM], g_perf_data[AENC][AENC_ENCODE_FRAME]);
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m, "-----------------------------------------------------AENC PERF STATISTIC END"
|
|
+ "-----------------------------------------------------\n");
|
|
+}
|
|
+
|
|
+static void show_adec_data(struct seq_file *m)
|
|
+{
|
|
+ seq_printf(m, "----------------------------------------------------ADEC PERF STATISTIC BEGIN"
|
|
+ "----------------------------------------------------\n");
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m, "--------------------------------------------------------------------------"
|
|
+ "--------------------------------------------------------\n");
|
|
+ seq_printf(m, "audio decoder performance(us):\n");
|
|
+ seq_printf(m, "%-14s%-14s%-14s%-14s%-14s\n", "create_chn", "send_stream", "get_frame",
|
|
+ "parse_packet", "decode_stream");
|
|
+ seq_printf(m, "------------------------------------------------------------------------------------"
|
|
+ "----------------------------------------------\n");
|
|
+ seq_printf(m, "%-14d%-14d%-14d%-14d%-14d\n", g_perf_data[ADEC][ADEC_CREATE_CHN],
|
|
+ g_perf_data[ADEC][ADEC_SEND_STREAM], g_perf_data[ADEC][ADEC_GET_FRAME],
|
|
+ g_perf_data[ADEC][ADEC_PARSE_PACKET], g_perf_data[ADEC][ADEC_DECODE_STREAM]);
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m,"-----------------------------------------------------ADEC PERF STATISTIC END"
|
|
+ "-----------------------------------------------------\n");
|
|
+}
|
|
+
|
|
+static void show_ao_data(struct seq_file *m)
|
|
+{
|
|
+ seq_printf(m,"----------------------------------------------------AO PERF STATISTIC BEGIN"
|
|
+ "----------------------------------------------------\n");
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m, "---------------------------------------------------------------------------"
|
|
+ "-------------------------------------------------------\n");
|
|
+ seq_printf(m, "audio output performance(us):\n");
|
|
+ seq_printf(m, "%-14s%-24s%-24s\n", "ao_start", "ao_process_frame", "ao_write_frame");
|
|
+ seq_printf(m, "----------------------------------------------------------------------------"
|
|
+ "------------------------------------------------------\n");
|
|
+ seq_printf(m, "%-14d%-24d%-24d\n", g_perf_data[AO][AO_START], g_perf_data[AO][AO_PROCESS_FRAME],
|
|
+ g_perf_data[AO][AO_WRITE_FRAME]);
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m, "-----------------------------------------------------------------------------"
|
|
+ "-----------------------------------------------------\n");
|
|
+ seq_printf(m, "audio argorithm performance(ns/1ms):\n");
|
|
+ seq_printf(m, "%-14s%-14s%-14s%-14s%-14s%-14s%-14s%-14s%-14s\n", "agc", "ans", "eq", "hpf",
|
|
+ "volume", "src-host","src-dai", "host", "dai");
|
|
+ seq_printf(m, "------------------------------------------------------------------------------"
|
|
+ "----------------------------------------------------\n");
|
|
+ seq_printf(m, "%-14d%-14d%-14d%-14d%-14d%-14d%-14d%-14d%-14d\n", g_perf_data[AO][AGC_PROCESS],
|
|
+ g_perf_data[AO][ANS_PROCESS],g_perf_data[AO][EQ_PROCESS],
|
|
+ g_perf_data[AO][DCBLOCK_PROCESS],g_perf_data[AO][VOLUME_PROCESS],
|
|
+ g_perf_data[AO][SRC_HOST_PROCESS],g_perf_data[AO][SRC_DAI_PROCESS],
|
|
+ g_perf_data[AO][HOST_PROCESS], g_perf_data[AO][DAI_PROCESS]);
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m,"-----------------------------------------------------AO PERF STATISTIC END"
|
|
+ "-----------------------------------------------------\n");
|
|
+}
|
|
+
|
|
+static void show_ai_data(struct seq_file *m)
|
|
+{
|
|
+ seq_printf(m,"----------------------------------------------------AI PERF STATISTIC BEGIN"
|
|
+ "----------------------------------------------------\n");
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m, "---------------------------------------------------------------------------"
|
|
+ "-------------------------------------------------------\n");
|
|
+ seq_printf(m, "audio input performance(us):\n");
|
|
+ seq_printf(m, "%-14s%-24s%-24s\n", "ai_start", "ai_read_frame", "ai_process_frame");
|
|
+ seq_printf(m, "----------------------------------------------------------------------------"
|
|
+ "------------------------------------------------------\n");
|
|
+ seq_printf(m, "%-14d%-24d%-24d\n", g_perf_data[AI][AI_START], g_perf_data[AI][AI_READ_FRAME],
|
|
+ g_perf_data[AI][AI_PROCESS_FRAME]);
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m, "-----------------------------------------------------------------------------"
|
|
+ "-----------------------------------------------------\n");
|
|
+ seq_printf(m, "audio argorithm performance(ns/1ms):\n");
|
|
+ seq_printf(m, "%-14s%-14s%-14s%-14s%-14s%-14s%-14s%-14s%-14s%-14s\n", "agc", "ans", "drc", "eq",
|
|
+ "hpf", "volume","src-host", "src-dai", "host", "dai");
|
|
+ seq_printf(m, "------------------------------------------------------------------------------"
|
|
+ "----------------------------------------------------\n");
|
|
+ seq_printf(m, "%-14d%-14d%-14d%-14d%-14d%-14d%-14d%-14d%-14d%-14d\n", g_perf_data[AI][AGC_PROCESS],
|
|
+ g_perf_data[AI][ANS_PROCESS],g_perf_data[AI][DRC_PROCESS], g_perf_data[AI][EQ_PROCESS],
|
|
+ g_perf_data[AI][DCBLOCK_PROCESS], g_perf_data[AI][VOLUME_PROCESS],g_perf_data[AI][SRC_HOST_PROCESS],
|
|
+ g_perf_data[AI][SRC_DAI_PROCESS], g_perf_data[AI][HOST_PROCESS], g_perf_data[AI][DAI_PROCESS]);
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m,"-----------------------------------------------------AI PERF STATISTIC END"
|
|
+ "-----------------------------------------------------\n");
|
|
+}
|
|
+
|
|
+
|
|
+static int audio_info_show(struct seq_file *m, void *p)
|
|
+{
|
|
+ int i;
|
|
+ const char *fileName = m->file->f_path.dentry->d_name.name;
|
|
+ enum DEVICES_ID deviceID = INVALID_DEVICE;
|
|
+
|
|
+ pr_info("audio_info_show:%s\n", m->file->f_path.dentry->d_name.name);
|
|
+
|
|
+ for (i = 0; i < NUM_DEVICES; ++i) {
|
|
+ if (strcmp(fileName, device_names[i]) == 0) {
|
|
+ deviceID = i;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (deviceID == INVALID_DEVICE) {
|
|
+ pr_err("deviceID is INVALID\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (g_switch == 0) {
|
|
+ seq_printf(m, "The switch is not turned on, pls first turn on the switch.\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ switch (deviceID) {
|
|
+ case AI:
|
|
+ show_ai_data(m);
|
|
+ break;
|
|
+ case AO:
|
|
+ show_ao_data(m);
|
|
+ break;
|
|
+ case AENC:
|
|
+ show_aenc_data(m);
|
|
+ break;
|
|
+ case ADEC:
|
|
+ show_adec_data(m);
|
|
+ break;
|
|
+ default:
|
|
+ pr_err("deviceID is INVALID\n");
|
|
+ break;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int info_open(struct inode *inode, struct file *flip)
|
|
+{
|
|
+ return single_open(flip, audio_info_show, NULL);
|
|
+}
|
|
+
|
|
+static int switch_show(struct seq_file *m, void *p)
|
|
+{
|
|
+ seq_printf(m, "--------------------AUDIO Performance Switch--------------------\n");
|
|
+ seq_printf(m, "\n");
|
|
+ seq_printf(m, "AUDIO Performance Switch Status Value:%d\n", g_switch);
|
|
+ seq_printf(m, "\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int switch_open(struct inode *inode, struct file *flip)
|
|
+{
|
|
+ return single_open(flip, switch_show, NULL);
|
|
+}
|
|
+
|
|
+static ssize_t switch_write(struct file *flip, const char __user *buf, size_t size, loff_t *pos)
|
|
+{
|
|
+ u16 data;
|
|
+ u8 value;
|
|
+
|
|
+ if (size > 2) {
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (copy_from_user(&data, buf, size)) {
|
|
+ return -EFAULT;
|
|
+ }
|
|
+ value = data & 0xff;
|
|
+
|
|
+ value -= '0';
|
|
+ if (!(value == 1 || value == 0)) {
|
|
+ printk("%s, %d, data=%d is not correct, pls use 1 or 0.\n", __func__, __LINE__, value);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ g_switch = value ? 1 : 0;
|
|
+
|
|
+ return size;
|
|
+}
|
|
+
|
|
+static struct proc_ops proc_info_fops = {
|
|
+ .proc_open = info_open,
|
|
+ .proc_read = seq_read,
|
|
+ .proc_release = single_release,
|
|
+};
|
|
+
|
|
+static struct proc_ops proc_switch_fops = {
|
|
+ .proc_open = switch_open,
|
|
+ .proc_read = seq_read,
|
|
+ .proc_release = single_release,
|
|
+ .proc_write = switch_write,
|
|
+};
|
|
+
|
|
+int audio_create_procfs(void)
|
|
+{
|
|
+ proc_esaudio = proc_mkdir("es_audio", NULL);
|
|
+ if (proc_esaudio == NULL) {
|
|
+ pr_err("create es_audio dir err.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ if (!proc_create("ao", 0644, proc_esaudio, &proc_info_fops)) {
|
|
+ pr_err("error create proc ao file.\n");
|
|
+ goto err_ao;
|
|
+ }
|
|
+
|
|
+ if (!proc_create("ai", 0644, proc_esaudio, &proc_info_fops)) {
|
|
+ pr_err("error create proc ai file.\n");
|
|
+ goto err_ai;
|
|
+ }
|
|
+
|
|
+ if (!proc_create("aenc", 0644, proc_esaudio, &proc_info_fops)) {
|
|
+ pr_err("error create proc aenc file.\n");
|
|
+ goto err_aenc;
|
|
+ }
|
|
+
|
|
+ if (!proc_create("adec", 0644, proc_esaudio, &proc_info_fops)) {
|
|
+ pr_err("error create proc adec file.\n");
|
|
+ goto err_adec;
|
|
+ }
|
|
+
|
|
+ if (!proc_create("switch", 0644, proc_esaudio, &proc_switch_fops)) {
|
|
+ pr_err("error create proc switch file.\n");
|
|
+ goto err_switch;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_switch:
|
|
+ remove_proc_entry("adec", proc_esaudio);
|
|
+err_adec:
|
|
+ remove_proc_entry("aenc", proc_esaudio);
|
|
+err_aenc:
|
|
+ remove_proc_entry("ai", proc_esaudio);
|
|
+err_ai:
|
|
+ remove_proc_entry("ao", proc_esaudio);
|
|
+err_ao:
|
|
+ remove_proc_entry("es_audio", NULL);
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+void audio_remove_procfs(void)
|
|
+{
|
|
+ remove_proc_entry("switch", proc_esaudio);
|
|
+
|
|
+ remove_proc_entry("adec", proc_esaudio);
|
|
+
|
|
+ remove_proc_entry("aenc", proc_esaudio);
|
|
+
|
|
+ remove_proc_entry("ai", proc_esaudio);
|
|
+
|
|
+ remove_proc_entry("ao", proc_esaudio);
|
|
+
|
|
+ remove_proc_entry("es_audio", NULL);
|
|
+}
|
|
+
|
|
+
|
|
+///////////////////////////////////////////////////////////////////////////////
|
|
+// audio dev implementation
|
|
+static int audio_dev_mmap(struct file *file, struct vm_area_struct *vma)
|
|
+{
|
|
+ int i;
|
|
+ const char *fileName = file->f_path.dentry->d_name.name;
|
|
+ enum DEVICES_ID deviceID = INVALID_DEVICE;
|
|
+ unsigned long size = vma->vm_end - vma->vm_start;
|
|
+
|
|
+ pr_info("audio_dev_mmap:%s\n", file->f_path.dentry->d_name.name);
|
|
+
|
|
+ pr_info("vma->vm_end:%ld,vma->vm_start:%ld\n",vma->vm_end, vma->vm_start);
|
|
+
|
|
+ if (size > (MAX_PERF_SIZE * sizeof(int32_t))) {
|
|
+ pr_err("audio_dev_mmap: size:%ld > %ld.\n", size, MAX_PERF_SIZE * sizeof(int32_t));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < NUM_DEVICES; ++i) {
|
|
+ if (strcmp(fileName, device_names[i]) == 0) {
|
|
+ deviceID = i;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (deviceID == INVALID_DEVICE) {
|
|
+ pr_err("deviceID is INVALID\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ // Remap the shared memory into the process's address space
|
|
+ if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(g_perf_data[deviceID]) >> PAGE_SHIFT,
|
|
+ size, vma->vm_page_prot)) {
|
|
+ pr_err("Failed to remap shared memory.\n");
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct file_operations dev_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .mmap = audio_dev_mmap,
|
|
+};
|
|
+
|
|
+static bool g_proc_initialized = false;
|
|
+
|
|
+int audio_proc_module_init(void)
|
|
+{
|
|
+ int i, ret;
|
|
+ struct device *dev;
|
|
+
|
|
+ if (g_proc_initialized) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ pr_info("audio_proc_module_init enter.\n");
|
|
+
|
|
+ audio_proc_class = class_create("audio_proc_class");
|
|
+ if (IS_ERR(audio_proc_class)) {
|
|
+ pr_err("Failed to create audio_proc_class\n");
|
|
+ return PTR_ERR(audio_proc_class);
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < NUM_DEVICES; ++i) {
|
|
+ g_perf_data[i] = kmalloc(MAX_PERF_SIZE * sizeof(int32_t), GFP_KERNEL);
|
|
+ if (!g_perf_data[i]) {
|
|
+ pr_err("Failed to allocate shared memory for '%s'\n", device_names[i]);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ memset(g_perf_data[i], 0, MAX_PERF_SIZE * sizeof(int32_t));
|
|
+
|
|
+ ret = register_chrdev(0, device_names[i], &dev_fops);
|
|
+ if (ret < 0) {
|
|
+ pr_err("Failed to register character device '%s'\n", device_names[i]);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ audio_proc_major[i] = ret;
|
|
+
|
|
+ dev = device_create(audio_proc_class, NULL, MKDEV(audio_proc_major[i], 0), NULL, device_names[i]);
|
|
+ if (IS_ERR(dev)) {
|
|
+ pr_err("Failed to create device node '%s'\n", device_names[i]);
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ audio_proc_device[i] = dev;
|
|
+ }
|
|
+
|
|
+ audio_create_procfs();
|
|
+
|
|
+ g_proc_initialized = true;
|
|
+
|
|
+ pr_info("es_audio_proc: initialized\n");
|
|
+ return 0;
|
|
+
|
|
+cleanup:
|
|
+ for (i = 0; i < NUM_DEVICES; ++i) {
|
|
+ if (g_perf_data[i]) {
|
|
+ kfree(g_perf_data[i]);
|
|
+ g_perf_data[i] = NULL;
|
|
+ }
|
|
+
|
|
+ if (audio_proc_major[i]) {
|
|
+ unregister_chrdev(audio_proc_major[i], device_names[i]);
|
|
+ }
|
|
+
|
|
+ if (audio_proc_device[i]) {
|
|
+ device_destroy(audio_proc_class, MKDEV(audio_proc_major[i], 0));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ class_destroy(audio_proc_class);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static bool g_proc_uninitialized = false;
|
|
+
|
|
+void audio_proc_module_exit(void)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ if (g_proc_uninitialized) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ audio_remove_procfs();
|
|
+ for (i = 0; i < NUM_DEVICES; ++i) {
|
|
+ device_destroy(audio_proc_class, MKDEV(audio_proc_major[i], 0));
|
|
+ unregister_chrdev(audio_proc_major[i], device_names[i]);
|
|
+ kfree(g_perf_data[i]);
|
|
+ g_perf_data[i] = NULL;
|
|
+ }
|
|
+
|
|
+ class_destroy(audio_proc_class);
|
|
+
|
|
+ g_proc_uninitialized = true;
|
|
+
|
|
+ pr_info("es_audio_proc: uninitialized\n");
|
|
+}
|
|
diff --git a/sound/soc/eswin/esw-audio-proc.h b/sound/soc/eswin/esw-audio-proc.h
|
|
new file mode 100644
|
|
index 000000000000..1fd3f377671d
|
|
--- /dev/null
|
|
+++ b/sound/soc/eswin/esw-audio-proc.h
|
|
@@ -0,0 +1,23 @@
|
|
+/*
|
|
+ *
|
|
+ * Copyright (C) 2021 ESWIN, Inc. All rights reserved.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
+ * more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef _ES_AUDIO_PROC_H_
|
|
+#define _ES_AUDIO_PROC_H_
|
|
+
|
|
+int audio_proc_module_init(void);
|
|
+void audio_proc_module_exit(void);
|
|
+
|
|
+#endif
|
|
diff --git a/sound/soc/eswin/esw-i2s.c b/sound/soc/eswin/esw-i2s.c
|
|
new file mode 100755
|
|
index 000000000000..8fe165ddb0d6
|
|
--- /dev/null
|
|
+++ b/sound/soc/eswin/esw-i2s.c
|
|
@@ -0,0 +1,973 @@
|
|
+/*
|
|
+ * ALSA SoC Synopsys I2S Audio Layer
|
|
+ *
|
|
+ * sound/soc/dwc/designware_i2s.c
|
|
+ *
|
|
+ * Copyright (C) 2010 ST Microelectronics
|
|
+ * Rajeev Kumar <rajeevkumar.linux@gmail.com>
|
|
+ *
|
|
+ * This file is licensed under the terms of the GNU General Public
|
|
+ * License version 2. This program is licensed "as is" without any
|
|
+ * warranty of any kind, whether express or implied.
|
|
+ *
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * Copyright (C) 2021 ESWIN, Inc. All rights reserved.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
+ * more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <sound/pcm.h>
|
|
+#include <linux/device.h>
|
|
+#include <sound/soc.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+#include <sound/pcm_params.h>
|
|
+#include <asm/io.h>
|
|
+#include <sound/asound.h>
|
|
+#include <sound/designware_i2s.h>
|
|
+#include <linux/irqreturn.h>
|
|
+#include <linux/clk.h>
|
|
+#include <sound/soc-dai.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/pm.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/dma-map-ops.h>
|
|
+#include "esw-i2s.h"
|
|
+#include "esw-audio-proc.h"
|
|
+
|
|
+#define VO_MCLK_DIVSOR_MASK 0xff0
|
|
+#define VO_MCLK_DIVSOR_OFFSET 4
|
|
+
|
|
+#define MAX_SAMPLE_RATE_SUPPORT (192000UL)
|
|
+#define MAX_SAMPLE_RATE_CLK (MAX_SAMPLE_RATE_SUPPORT * 32 * 2) // 32 bits, 2channels
|
|
+
|
|
+#define VO_TOP_CSR 0x50280000UL
|
|
+#define VO_I2S0_DIV_NUM 0x2000
|
|
+#define VO_I2S1_DIV_NUM 0x2004
|
|
+#define VO_I2S2_DIV_NUM 0x2008
|
|
+#define DIV_NUM_MASK 0x1f
|
|
+
|
|
+#define ESW_I2S_RATES (SNDRV_PCM_RATE_192000 | \
|
|
+ SNDRV_PCM_RATE_96000 | \
|
|
+ SNDRV_PCM_RATE_48000 | \
|
|
+ SNDRV_PCM_RATE_32000 | \
|
|
+ SNDRV_PCM_RATE_16000 | \
|
|
+ SNDRV_PCM_RATE_8000)
|
|
+#define ESW_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
|
|
+
|
|
+static u32 dmaen_txch[] = {
|
|
+ DMAEN_TXCH_0,
|
|
+ DMAEN_TXCH_1,
|
|
+ DMAEN_TXCH_2,
|
|
+ DMAEN_TXCH_3
|
|
+};
|
|
+
|
|
+static u32 dmaen_rxch[] = {
|
|
+ DMAEN_RXCH_0,
|
|
+ DMAEN_RXCH_1,
|
|
+ DMAEN_RXCH_2,
|
|
+ DMAEN_RXCH_3
|
|
+};
|
|
+
|
|
+/* Maximum bit resolution of a channel - not uniformly spaced */
|
|
+static const u32 fifo_width[COMP_MAX_WORDSIZE] = {
|
|
+ 12, 16, 20, 24, 32, 0, 0, 0
|
|
+};
|
|
+
|
|
+/* Width of (DMA) bus */
|
|
+static const u32 bus_widths[COMP_MAX_DATA_WIDTH] = {
|
|
+ DMA_SLAVE_BUSWIDTH_1_BYTE,
|
|
+ DMA_SLAVE_BUSWIDTH_2_BYTES,
|
|
+ DMA_SLAVE_BUSWIDTH_4_BYTES,
|
|
+ DMA_SLAVE_BUSWIDTH_UNDEFINED
|
|
+};
|
|
+
|
|
+static inline u32 i2s_read_reg(void *io_base, int reg)
|
|
+{
|
|
+ return readl((char *)io_base + reg);
|
|
+}
|
|
+
|
|
+static inline void i2s_write_reg(void *io_base, int reg, u32 val)
|
|
+{
|
|
+ writel(val, (char *)io_base + reg);
|
|
+}
|
|
+
|
|
+static inline void i2s_disable_channels(struct i2s_dev *i2s_drvdata, u32 stream)
|
|
+{
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, TER(0), 0);
|
|
+ } else {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, RER(0), 0);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void i2s_config(struct i2s_dev *i2s_drvdata, int stream)
|
|
+{
|
|
+ u32 ch_reg;
|
|
+ struct i2s_clk_config_data *config = &i2s_drvdata->config;
|
|
+ i2s_disable_channels(i2s_drvdata, stream);
|
|
+ for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) {
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, TCR(ch_reg),
|
|
+ i2s_drvdata->xfer_resolution);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, TFCR(ch_reg),
|
|
+ i2s_drvdata->fifo_th - 1);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, TER(ch_reg), 1);
|
|
+ } else {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, RCR(ch_reg),
|
|
+ i2s_drvdata->xfer_resolution);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, RFCR(ch_reg),
|
|
+ i2s_drvdata->fifo_th - 1);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, RER(ch_reg), 1);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void i2s_enable_irqs(struct i2s_dev *i2s_drvdata, u32 stream,
|
|
+ int chan_nr)
|
|
+{
|
|
+ u32 i, irq;
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ for (i = 0; i < (chan_nr / 2); i++) {
|
|
+ irq = i2s_read_reg(i2s_drvdata->i2s_base, IMR(i));
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, IMR(i), irq & ~0x30);
|
|
+ }
|
|
+ } else {
|
|
+ for (i = 0; i < (chan_nr / 2); i++) {
|
|
+ irq = i2s_read_reg(i2s_drvdata->i2s_base, IMR(i));
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, IMR(i), irq & ~0x03);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void i2s_enable_dedicated_dma(struct i2s_dev *i2s_drvdata, u32 stream,
|
|
+ int chan_nr)
|
|
+{
|
|
+ u32 i, dmacr;
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ for (i = 0; i < (chan_nr / 2); i++) {
|
|
+ dmacr = i2s_read_reg(i2s_drvdata->i2s_base, DMACR);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, DMACR, dmacr | dmaen_txch[i]);
|
|
+ }
|
|
+ } else {
|
|
+ for (i = 0; i < (chan_nr / 2); i++) {
|
|
+ dmacr = i2s_read_reg(i2s_drvdata->i2s_base, DMACR);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, DMACR, dmacr | dmaen_rxch[i]);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void i2s_disable_dedicated_dma(struct i2s_dev *i2s_drvdata, u32 stream,
|
|
+ int chan_nr)
|
|
+{
|
|
+ u32 i, dmacr;
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ for (i = 0; i < (chan_nr / 2); i++) {
|
|
+ dmacr = i2s_read_reg(i2s_drvdata->i2s_base, DMACR);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, DMACR, dmacr & ~dmaen_txch[i]);
|
|
+ }
|
|
+ } else {
|
|
+ for (i = 0; i < (chan_nr / 2); i++) {
|
|
+ dmacr = i2s_read_reg(i2s_drvdata->i2s_base, DMACR);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, DMACR, dmacr & ~dmaen_rxch[i]);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void i2s_enable_combined_dma(struct i2s_dev *i2s_drvdata, u32 stream)
|
|
+{
|
|
+ u32 dmacr;
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ dmacr = i2s_read_reg(i2s_drvdata->i2s_base, DMACR);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, DMACR, dmacr | DMAEN_TXBLOCK);
|
|
+
|
|
+ } else {
|
|
+ dmacr = i2s_read_reg(i2s_drvdata->i2s_base, DMACR);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, DMACR, dmacr | DMAEN_RXBLOCK);
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void i2s_disable_combined_dma(struct i2s_dev *i2s_drvdata, u32 stream)
|
|
+{
|
|
+ u32 dmacr;
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ dmacr = i2s_read_reg(i2s_drvdata->i2s_base, DMACR);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, DMACR, dmacr & ~DMAEN_TXBLOCK);
|
|
+
|
|
+ } else {
|
|
+ dmacr = i2s_read_reg(i2s_drvdata->i2s_base, DMACR);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, DMACR, dmacr & ~DMAEN_RXBLOCK);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void i2s_start(struct i2s_dev *i2s_drvdata,
|
|
+ struct snd_pcm_substream *substream)
|
|
+{
|
|
+ struct i2s_clk_config_data *config = &i2s_drvdata->config;
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, IER, 1);
|
|
+ if (i2s_drvdata->use_pio) {
|
|
+ i2s_enable_irqs(i2s_drvdata, substream->stream, config->chan_nr);
|
|
+ }
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, ITER, 1);
|
|
+ } else {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, IRER, 1);
|
|
+ }
|
|
+ if (!i2s_drvdata->use_pio) {
|
|
+ i2s_enable_dedicated_dma(i2s_drvdata, substream->stream, config->chan_nr);
|
|
+ }
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, CER, 1);
|
|
+}
|
|
+
|
|
+static inline void i2s_clear_irqs(struct i2s_dev *i2s_drvdata, u32 stream)
|
|
+{
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ i2s_read_reg(i2s_drvdata->i2s_base, TOR(0));
|
|
+ } else {
|
|
+ i2s_read_reg(i2s_drvdata->i2s_base, ROR(0));
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void i2s_disable_irqs(struct i2s_dev *i2s_drvdata, u32 stream,
|
|
+ int chan_nr)
|
|
+{
|
|
+ u32 i, irq;
|
|
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ for (i = 0; i < (chan_nr / 2); i++) {
|
|
+ irq = i2s_read_reg(i2s_drvdata->i2s_base, IMR(i));
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, IMR(i), irq | 0x30);
|
|
+ }
|
|
+ } else {
|
|
+ for (i = 0; i < (chan_nr / 2); i++) {
|
|
+ irq = i2s_read_reg(i2s_drvdata->i2s_base, IMR(i));
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, IMR(i), irq | 0x03);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void i2s_stop(struct i2s_dev *i2s_drvdata,
|
|
+ struct snd_pcm_substream *substream)
|
|
+{
|
|
+ if (i2s_drvdata->use_pio) {
|
|
+ i2s_clear_irqs(i2s_drvdata, substream->stream);
|
|
+ }
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, ITER, 0);
|
|
+ } else {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, IRER, 0);
|
|
+ }
|
|
+ if (i2s_drvdata->use_pio) {
|
|
+ i2s_disable_irqs(i2s_drvdata, substream->stream, 2);
|
|
+ } else {
|
|
+ i2s_disable_dedicated_dma(i2s_drvdata, substream->stream, 2);
|
|
+ }
|
|
+ if (!i2s_drvdata->active) {
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, CER, 0);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, IER, 0);
|
|
+ }
|
|
+}
|
|
+
|
|
+#define COMP1_MAX_WORDSIZE 5
|
|
+static const u32 i2s_formats[COMP1_MAX_WORDSIZE] = {
|
|
+ SNDRV_PCM_FMTBIT_S16_LE,
|
|
+ SNDRV_PCM_FMTBIT_S16_LE,
|
|
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
|
|
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
|
|
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE
|
|
+};
|
|
+
|
|
+static int i2s_configure_dai(struct i2s_dev *i2s_drvdata,
|
|
+ struct snd_soc_dai_driver *i2s_dai,
|
|
+ unsigned int rates)
|
|
+{
|
|
+ u32 idx;
|
|
+ u32 fifo_depth;
|
|
+ u32 comp1, comp2;
|
|
+
|
|
+ comp1 = i2s_read_reg(i2s_drvdata->i2s_base, i2s_drvdata->i2s_reg_comp1);
|
|
+ comp2 = i2s_read_reg(i2s_drvdata->i2s_base, i2s_drvdata->i2s_reg_comp2);
|
|
+ fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
|
|
+
|
|
+ if (COMP1_TX_ENABLED(comp1)) {
|
|
+ dev_dbg(i2s_drvdata->dev, " i2s: play supported\n");
|
|
+ idx = COMP1_TX_WORDSIZE_0(comp1);
|
|
+ if (WARN_ON(idx >= ARRAY_SIZE(i2s_formats)))
|
|
+ return -EINVAL;
|
|
+ i2s_dai->playback.formats = i2s_formats[idx];
|
|
+ i2s_dai->playback.channels_min = MIN_CHANNEL_NUM;
|
|
+ i2s_dai->playback.channels_max =
|
|
+ (COMP1_TX_CHANNELS(comp1) + 1) << 1;
|
|
+ i2s_dai->playback.rates = rates;
|
|
+ }
|
|
+
|
|
+ if (COMP1_RX_ENABLED(comp1)){
|
|
+ dev_dbg(i2s_drvdata->dev, "i2s: record supported\n");
|
|
+ idx = COMP2_RX_WORDSIZE_0(comp2);
|
|
+ if (WARN_ON(idx >= ARRAY_SIZE(i2s_formats)))
|
|
+ return -EINVAL;
|
|
+ i2s_dai->capture.formats = i2s_formats[idx];
|
|
+ i2s_dai->capture.channels_min = MIN_CHANNEL_NUM;
|
|
+ i2s_dai->capture.channels_max =
|
|
+ (COMP1_RX_CHANNELS(comp1) + 1) << 1;
|
|
+ i2s_dai->capture.rates = rates;
|
|
+ }
|
|
+
|
|
+ if (COMP1_MODE_EN(comp1)) {
|
|
+ dev_dbg(i2s_drvdata->dev, "eswin: i2s master mode supported\n");
|
|
+ i2s_drvdata->capability |= DW_I2S_MASTER;
|
|
+ } else {
|
|
+ dev_dbg(i2s_drvdata->dev, "eswin: i2s slave mode supported\n");
|
|
+ i2s_drvdata->capability |= DW_I2S_SLAVE;
|
|
+ }
|
|
+ i2s_drvdata->fifo_th = fifo_depth / 2;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int i2s_configure_dai_by_dt(struct i2s_dev *dev,
|
|
+ struct snd_soc_dai_driver *i2s_dai,
|
|
+ struct resource *res)
|
|
+{
|
|
+ struct snd_soc_component *component;
|
|
+ struct dmaengine_pcm *pcm;
|
|
+ u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1);
|
|
+ u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2);
|
|
+ u32 fifo_depth;
|
|
+ u32 idx;
|
|
+ u32 idx2;
|
|
+ int ret;
|
|
+
|
|
+ dev_info(dev->dev, "comp1:0x%x, comp2:0x%x\n", comp1, comp2);
|
|
+ fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
|
|
+ idx = COMP1_APB_DATA_WIDTH(comp1);
|
|
+
|
|
+ if (WARN_ON(idx >= ARRAY_SIZE(bus_widths))) {
|
|
+ dev_err(dev->dev, "idx:%d inval\n", idx);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ ret = i2s_configure_dai(dev, i2s_dai, SNDRV_PCM_RATE_8000_192000);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev->dev, "i2s_configure_dai failed: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ component = snd_soc_lookup_component(dev->dev, SND_DMAENGINE_PCM_DRV_NAME);
|
|
+ if (!component) {
|
|
+ dev_err(dev->dev, "Can not find snd_soc_component\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ pcm = soc_component_to_pcm(component);
|
|
+ if (COMP1_TX_ENABLED(comp1)) {
|
|
+ idx2 = COMP1_TX_WORDSIZE_0(comp1);
|
|
+ dev->capability |= DWC_I2S_PLAY;
|
|
+ /* only configure Combined DMA addr, Our scenario is not Dedicated DMA case */
|
|
+ dev->play_dma_data.addr_width = bus_widths[idx];
|
|
+ dev->play_dma_data.fifo_size = fifo_depth *
|
|
+ (fifo_width[idx2]) >> 3;
|
|
+ if (of_node_name_prefix(pcm->chan[SNDRV_PCM_STREAM_PLAYBACK]->device->dev->of_node,
|
|
+ "dma-controller-hsp")) {
|
|
+ dev->play_dma_data.addr = dma_map_resource(
|
|
+ pcm->chan[SNDRV_PCM_STREAM_PLAYBACK]->device->dev,
|
|
+ res->start + TXDMA_CH(0),
|
|
+ dev->play_dma_data.fifo_size,
|
|
+ DMA_BIDIRECTIONAL,
|
|
+ DMA_ATTR_SKIP_CPU_SYNC);
|
|
+ } else {
|
|
+ dev->play_dma_data.addr = res->start + TXDMA_CH(0);
|
|
+ }
|
|
+ dev->play_dma_data.maxburst = 16;
|
|
+ }
|
|
+ if (COMP1_RX_ENABLED(comp1)) {
|
|
+ idx2 = COMP2_RX_WORDSIZE_0(comp2);
|
|
+ dev->capability |= DWC_I2S_RECORD;
|
|
+ /* only configure Combined DMA addr, Our scenario is not Dedicated DMA case */
|
|
+ dev->capture_dma_data.addr_width = bus_widths[idx];
|
|
+ dev->capture_dma_data.fifo_size = fifo_depth *
|
|
+ (fifo_width[idx2]) >> 3;
|
|
+ if (of_node_name_prefix(pcm->chan[SNDRV_PCM_STREAM_CAPTURE]->device->dev->of_node,
|
|
+ "dma-controller-hsp")) {
|
|
+ dev->capture_dma_data.addr = dma_map_resource(
|
|
+ pcm->chan[SNDRV_PCM_STREAM_CAPTURE]->device->dev,
|
|
+ res->start + RXDMA_CH(0),
|
|
+ dev->capture_dma_data.fifo_size,
|
|
+ DMA_BIDIRECTIONAL,
|
|
+ DMA_ATTR_SKIP_CPU_SYNC);
|
|
+ } else {
|
|
+ dev->capture_dma_data.addr = res->start + RXDMA_CH(0);
|
|
+ }
|
|
+ dev->capture_dma_data.maxburst = 16;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int i2s_startup(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *cpu_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 i2s_hw_params(struct snd_pcm_substream *substream,
|
|
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = snd_soc_dai_get_drvdata(dai);
|
|
+ struct i2s_clk_config_data *config = &i2s_drvdata->config;
|
|
+ struct device_node *node = i2s_drvdata->dev->of_node;
|
|
+ struct regmap *vo_mclk_sel_regmap;
|
|
+ uint32_t vo_mclk_sel_reg;
|
|
+ uint32_t vo_mclk_sel;
|
|
+ int ret;
|
|
+ uint32_t div_num = 0;
|
|
+ uint32_t div_num_reg;
|
|
+
|
|
+ dev_dbg(i2s_drvdata->dev, "sample rate:%d, chan:%d, width:%d\n",
|
|
+ params_rate(params), params_channels(params), params_width(params));
|
|
+ switch (params_format(params)) {
|
|
+ case SNDRV_PCM_FORMAT_S16_LE:
|
|
+ config->data_width = 16;
|
|
+ i2s_drvdata->ccr = CLOCK_CYCLES_32 << CCR_WSS_POS |
|
|
+ NO_CLOCK_GATING;
|
|
+ i2s_drvdata->xfer_resolution = RESOLUTION_16_BIT;
|
|
+ break;
|
|
+ case SNDRV_PCM_FORMAT_S24_LE:
|
|
+ config->data_width = 24;
|
|
+ i2s_drvdata->ccr = CLOCK_CYCLES_32 << CCR_WSS_POS |
|
|
+ NO_CLOCK_GATING;
|
|
+ i2s_drvdata->xfer_resolution = RESOLUTION_24_BIT;
|
|
+ break;
|
|
+ case SNDRV_PCM_FORMAT_S32_LE:
|
|
+ config->data_width = 32;
|
|
+ i2s_drvdata->ccr = CLOCK_CYCLES_32 << CCR_WSS_POS |
|
|
+ NO_CLOCK_GATING;
|
|
+ i2s_drvdata->xfer_resolution = RESOLUTION_32_BIT;
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(i2s_drvdata->dev, "eswin-i2s: unsupported PCM fmt");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ config->chan_nr = MAX_CHANNEL_NUM;
|
|
+ switch (config->chan_nr) {
|
|
+ case TWO_CHANNEL_SUPPORT:
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(i2s_drvdata->dev, "channel not supported\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ i2s_config(i2s_drvdata, substream->stream);
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, CCR, i2s_drvdata->ccr);
|
|
+ config->sample_rate = params_rate(params);
|
|
+ if (i2s_drvdata->capability & DW_I2S_MASTER) {
|
|
+ if (!i2s_drvdata->eswin_plat) {
|
|
+ vo_mclk_sel_regmap =
|
|
+ syscon_regmap_lookup_by_phandle(node, "vo_mclk_sel,syscrg");
|
|
+ if (IS_ERR(vo_mclk_sel_regmap)) {
|
|
+ dev_err(i2s_drvdata->dev, "No vo_mclk_sel,syscrg phandle specified\n");
|
|
+ return PTR_ERR(vo_mclk_sel_regmap);
|
|
+ }
|
|
+ ret = of_property_read_u32_index(node, "vo_mclk_sel,syscrg", 1,
|
|
+ &vo_mclk_sel_reg);
|
|
+ if (ret) {
|
|
+ dev_err(i2s_drvdata->dev, "can't get vo_mclk_sel_reg offset (%d)\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ regmap_read(vo_mclk_sel_regmap, vo_mclk_sel_reg, &vo_mclk_sel);
|
|
+ vo_mclk_sel &= ~VO_MCLK_DIVSOR_MASK;
|
|
+
|
|
+ switch (config->sample_rate) {
|
|
+ case 96000:
|
|
+ vo_mclk_sel |= (0x10 << VO_MCLK_DIVSOR_OFFSET);
|
|
+ break;
|
|
+ case 48000:
|
|
+ vo_mclk_sel |= (0x12 << VO_MCLK_DIVSOR_OFFSET);
|
|
+ break;
|
|
+ case 44100:
|
|
+ vo_mclk_sel |= (0x11 << VO_MCLK_DIVSOR_OFFSET);
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(i2s_drvdata->dev, "Can't support sample rate: %d\n",
|
|
+ config->sample_rate);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ regmap_write(vo_mclk_sel_regmap, vo_mclk_sel_reg, vo_mclk_sel);
|
|
+ } else {
|
|
+ if (MAX_SAMPLE_RATE_SUPPORT % config->sample_rate != 0) {
|
|
+ dev_err(i2s_drvdata->dev, "Not support sample rate: %d\n", config->sample_rate);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ div_num = MAX_SAMPLE_RATE_SUPPORT / config->sample_rate - 1;
|
|
+
|
|
+ if (i2s_drvdata->active) {
|
|
+ if (i2s_drvdata->i2s_div_num != div_num) {
|
|
+ dev_err(i2s_drvdata->dev, "Not support the playback and capture clocks are different\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ } else {
|
|
+ div_num_reg = i2s_read_reg(i2s_drvdata->i2s_div_base, 0) & ~DIV_NUM_MASK;
|
|
+ div_num_reg |= div_num;
|
|
+
|
|
+ dev_dbg(i2s_drvdata->dev, "div num:0x%x\n", div_num);
|
|
+ i2s_drvdata->i2s_div_num = div_num;
|
|
+ i2s_write_reg(i2s_drvdata->i2s_div_base, 0, div_num_reg);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int i2s_prepare(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = snd_soc_dai_get_drvdata(dai);
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, TXFFR, 1);
|
|
+ else
|
|
+ i2s_write_reg(i2s_drvdata->i2s_base, RXFFR, 1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int i2s_trigger(struct snd_pcm_substream *substream,
|
|
+ int cmd, struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = 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:
|
|
+ i2s_drvdata->active++;
|
|
+ i2s_start(i2s_drvdata, substream);
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ i2s_drvdata->playback_active = true;
|
|
+ } else {
|
|
+ i2s_drvdata->capture_active = true;
|
|
+ }
|
|
+ break;
|
|
+ case SNDRV_PCM_TRIGGER_STOP:
|
|
+ case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
+ i2s_drvdata->active--;
|
|
+ i2s_stop(i2s_drvdata, substream);
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ i2s_drvdata->playback_active = false;
|
|
+ } else {
|
|
+ i2s_drvdata->capture_active = false;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
|
|
+{
|
|
+ struct i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
|
|
+ int ret = 0;
|
|
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
|
|
+ case SND_SOC_DAIFMT_BC_FC:
|
|
+ if (dev->capability & DW_I2S_SLAVE)
|
|
+ ret = 0;
|
|
+ else
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_BP_FP:
|
|
+ if (dev->capability & DW_I2S_MASTER)
|
|
+ ret = 0;
|
|
+ else
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_BC_FP:
|
|
+ case SND_SOC_DAIFMT_BP_FC:
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ default:
|
|
+ dev_dbg(dev->dev, "dwc : Invalid clock provider format\n");
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int i2s_pcm_dai_probe(struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = snd_soc_dai_get_drvdata(dai);
|
|
+
|
|
+ snd_soc_dai_init_dma_data(dai, &i2s_drvdata->play_dma_data, &i2s_drvdata->capture_dma_data);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_soc_dai_ops i2s_dai_ops = {
|
|
+ .probe = i2s_pcm_dai_probe,
|
|
+ .startup = i2s_startup,
|
|
+ .hw_params = i2s_hw_params,
|
|
+ .prepare = i2s_prepare,
|
|
+ .trigger = i2s_trigger,
|
|
+ .set_fmt = i2s_set_fmt,
|
|
+};
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int i2s_runtime_suspend(struct device *dev)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = dev_get_drvdata(dev);
|
|
+ if (i2s_drvdata->capability & DW_I2S_MASTER)
|
|
+ clk_disable(i2s_drvdata->clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int i2s_runtime_resume(struct device *dev)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = dev_get_drvdata(dev);
|
|
+ if (i2s_drvdata->capability & DW_I2S_MASTER)
|
|
+ clk_enable(i2s_drvdata->clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int i2s_suspend(struct snd_soc_component *component)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = snd_soc_component_get_drvdata(component);
|
|
+ if (i2s_drvdata->capability & DW_I2S_MASTER) {
|
|
+ clk_disable(i2s_drvdata->clk);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int i2s_resume(struct snd_soc_component *component)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = snd_soc_component_get_drvdata(component);
|
|
+ struct snd_soc_dai *dai = NULL;
|
|
+ int stream;
|
|
+
|
|
+ if (i2s_drvdata->capability & DW_I2S_MASTER)
|
|
+ clk_enable(i2s_drvdata->clk);
|
|
+
|
|
+ for_each_component_dais(component, dai) {
|
|
+ for_each_pcm_streams(stream)
|
|
+ if (snd_soc_dai_stream_active(dai, stream))
|
|
+ i2s_config(i2s_drvdata, stream);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#else
|
|
+#define i2s_suspend NULL
|
|
+#define i2s_resume NULL
|
|
+#endif
|
|
+
|
|
+static int i2s_reset(struct platform_device *pdev, struct i2s_dev *i2s)
|
|
+{
|
|
+ struct reset_control *rst;
|
|
+ struct reset_control *prst;
|
|
+ struct reset_control *voprst;
|
|
+ int ret;
|
|
+
|
|
+ rst = devm_reset_control_get_optional_exclusive(&pdev->dev, "i2srst");
|
|
+ if (IS_ERR(rst)) {
|
|
+ return PTR_ERR(rst);
|
|
+ }
|
|
+
|
|
+ prst = devm_reset_control_get_optional_exclusive(&pdev->dev, "i2sprst");
|
|
+ if (IS_ERR(prst)) {
|
|
+ return PTR_ERR(prst);
|
|
+ }
|
|
+
|
|
+ voprst = devm_reset_control_get_optional_exclusive(&pdev->dev, "voprst");
|
|
+ if (IS_ERR(prst)) {
|
|
+ return PTR_ERR(prst);
|
|
+ }
|
|
+
|
|
+ ret = reset_control_assert(rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_assert(prst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_deassert(rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_deassert(prst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_deassert(voprst);
|
|
+ WARN_ON(0 != ret);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int i2s_open(struct snd_soc_component *component,
|
|
+ struct snd_pcm_substream *substream)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK && i2s_drvdata->playback_active == true) ||
|
|
+ (substream->stream == SNDRV_PCM_STREAM_CAPTURE && i2s_drvdata->capture_active == true)) {
|
|
+ dev_err(i2s_drvdata->dev, "i2s is busying\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_soc_component_driver i2s_component = {
|
|
+ .name = "i2s",
|
|
+ .open = i2s_open,
|
|
+ .suspend = i2s_suspend,
|
|
+ .resume = i2s_resume,
|
|
+};
|
|
+
|
|
+static struct snd_soc_dai_driver i2s_dai[4] = {
|
|
+ {
|
|
+ .name = "i2s0-hdmi",
|
|
+ .id = 0,
|
|
+ .ops = &i2s_dai_ops,
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ESW_I2S_RATES,
|
|
+ .formats = ESW_I2S_FORMATS,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ESW_I2S_RATES,
|
|
+ .formats = ESW_I2S_FORMATS,
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .name = "i2s0",
|
|
+ .id = 1,
|
|
+ .ops = &i2s_dai_ops,
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ESW_I2S_RATES,
|
|
+ .formats = ESW_I2S_FORMATS,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ESW_I2S_RATES,
|
|
+ .formats = ESW_I2S_FORMATS,
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .name = "i2s1",
|
|
+ .id = 0,
|
|
+ .ops = &i2s_dai_ops,
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ESW_I2S_RATES,
|
|
+ .formats = ESW_I2S_FORMATS,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ESW_I2S_RATES,
|
|
+ .formats = ESW_I2S_FORMATS,
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ .name = "i2s2",
|
|
+ .id = 0,
|
|
+ .ops = &i2s_dai_ops,
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ESW_I2S_RATES,
|
|
+ .formats = ESW_I2S_FORMATS,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = MIN_CHANNEL_NUM,
|
|
+ .channels_max = MAX_CHANNEL_NUM,
|
|
+ .rates = ESW_I2S_RATES,
|
|
+ .formats = ESW_I2S_FORMATS,
|
|
+ },
|
|
+ },
|
|
+};
|
|
+
|
|
+static int i2s_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata;
|
|
+ struct resource *res;
|
|
+ int ret;
|
|
+ const char *clk_id;
|
|
+ struct snd_dmaengine_pcm_config *config;
|
|
+
|
|
+ dev_info(&pdev->dev, "dev name:%s\n", pdev->dev.of_node->name);
|
|
+ i2s_drvdata = devm_kzalloc(&pdev->dev, sizeof(*i2s_drvdata), GFP_KERNEL);
|
|
+ if (!i2s_drvdata)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ config = devm_kzalloc(&pdev->dev,
|
|
+ sizeof(struct snd_dmaengine_pcm_config), GFP_KERNEL);
|
|
+ if (!config)
|
|
+ return -ENOMEM;
|
|
+ config->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = "tx";
|
|
+ config->chan_names[SNDRV_PCM_STREAM_CAPTURE] = "rx";
|
|
+ config->prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config;
|
|
+
|
|
+ res = platform_get_resource(pdev,IORESOURCE_MEM, 0);
|
|
+ i2s_drvdata->i2s_base = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (IS_ERR(i2s_drvdata->i2s_base)) {
|
|
+ dev_err(&pdev->dev, "devm_ioremap_resource failed\n");
|
|
+ return PTR_ERR(i2s_drvdata->i2s_base);
|
|
+ }
|
|
+ i2s_drvdata->dev = &pdev->dev;
|
|
+
|
|
+ clk_id = "mclk";
|
|
+ if (of_node_name_prefix(pdev->dev.of_node, "i2s0")) {
|
|
+ i2s_drvdata->clk = devm_clk_get(&pdev->dev, clk_id);
|
|
+ if (IS_ERR(i2s_drvdata->clk))
|
|
+ return PTR_ERR(i2s_drvdata->clk);
|
|
+ ret = clk_prepare_enable(i2s_drvdata->clk);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ ret = clk_set_rate(i2s_drvdata->clk, MAX_SAMPLE_RATE_CLK);
|
|
+ if (ret) {
|
|
+ dev_err(i2s_drvdata->dev, "Can't set I2S clock rate: %d\n", ret);
|
|
+ }
|
|
+
|
|
+ ret = i2s_reset(pdev, i2s_drvdata);
|
|
+ if (ret != 0) {
|
|
+ dev_err(&pdev->dev, "i2s_reset failed\n");
|
|
+ goto err_probe;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dev_set_drvdata(&pdev->dev, i2s_drvdata);
|
|
+
|
|
+ if (of_node_name_prefix(pdev->dev.of_node, "i2s0")) {
|
|
+ i2s_drvdata->i2s_div_base = devm_ioremap(i2s_drvdata->dev, VO_TOP_CSR + VO_I2S0_DIV_NUM, 4);
|
|
+ if (!i2s_drvdata->i2s_div_base) {
|
|
+ dev_err(&pdev->dev, "failed to remap i2s0 div config\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ ret = devm_snd_soc_register_component(&pdev->dev, &i2s_component,
|
|
+ &i2s_dai[0], 2);
|
|
+ } else if (of_node_name_prefix(pdev->dev.of_node, "i2s1")) {
|
|
+ i2s_drvdata->i2s_div_base = devm_ioremap(i2s_drvdata->dev, VO_TOP_CSR + VO_I2S1_DIV_NUM, 4);
|
|
+ if (!i2s_drvdata->i2s_div_base) {
|
|
+ dev_err(&pdev->dev, "failed to remap i2s1 div config\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ ret = devm_snd_soc_register_component(&pdev->dev, &i2s_component,
|
|
+ &i2s_dai[2], 1);
|
|
+ } else {
|
|
+ i2s_drvdata->i2s_div_base = devm_ioremap(i2s_drvdata->dev, VO_TOP_CSR + VO_I2S2_DIV_NUM, 4);
|
|
+ if (!i2s_drvdata->i2s_div_base) {
|
|
+ dev_err(&pdev->dev, "failed to remap i2s2 div config\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ ret = devm_snd_soc_register_component(&pdev->dev, &i2s_component,
|
|
+ &i2s_dai[3], 1);
|
|
+ }
|
|
+ if (ret != 0) {
|
|
+ dev_err(&pdev->dev, "not able to register dai\n");
|
|
+ goto err_probe;
|
|
+ }
|
|
+
|
|
+ i2s_drvdata->use_pio = false;
|
|
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, config, 0);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "could not register pcm: %d\n", ret);
|
|
+ goto err_probe;
|
|
+ }
|
|
+
|
|
+ i2s_drvdata->i2s_reg_comp1 = I2S_COMP_PARAM_1;
|
|
+ i2s_drvdata->i2s_reg_comp2 = I2S_COMP_PARAM_2;
|
|
+ ret = i2s_configure_dai_by_dt(i2s_drvdata, &i2s_dai[0], res);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&pdev->dev, "i2s_configure_dai_by_dt failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = device_property_read_u32(&pdev->dev, "eswin-plat", &i2s_drvdata->eswin_plat);
|
|
+ if (0 != ret) {
|
|
+ dev_warn(&pdev->dev, "Failed to get eswin platform\n");
|
|
+ i2s_drvdata->eswin_plat = 0;
|
|
+ }
|
|
+ dev_info(&pdev->dev, "eswin platform:%d\n", i2s_drvdata->eswin_plat);
|
|
+
|
|
+ pm_runtime_enable(&pdev->dev);
|
|
+
|
|
+ audio_proc_module_init();
|
|
+
|
|
+ return 0;
|
|
+err_probe:
|
|
+ if (i2s_drvdata->capability & DW_I2S_MASTER)
|
|
+ clk_disable_unprepare(i2s_drvdata->clk);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int i2s_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct i2s_dev *i2s_drvdata = dev_get_drvdata(&pdev->dev);
|
|
+ if (i2s_drvdata->capability & DW_I2S_MASTER)
|
|
+ clk_disable_unprepare(i2s_drvdata->clk);
|
|
+
|
|
+ pm_runtime_disable(&pdev->dev);
|
|
+
|
|
+ audio_proc_module_exit();
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_OF
|
|
+static const struct of_device_id i2s_of_match[] = {
|
|
+ { .compatible = "snps,i2s", },
|
|
+ {},
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(of, i2s_of_match);
|
|
+#endif
|
|
+
|
|
+static const struct dev_pm_ops i2s_pm_ops = {
|
|
+ SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume, NULL)
|
|
+};
|
|
+
|
|
+static struct platform_driver i2s_driver = {
|
|
+ .probe = i2s_probe,
|
|
+ .remove = i2s_remove,
|
|
+ .driver = {
|
|
+ .name = "i2s",
|
|
+ .of_match_table = of_match_ptr(i2s_of_match),
|
|
+ .pm = &i2s_pm_ops,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(i2s_driver);
|
|
+
|
|
+MODULE_AUTHOR("ESWIN, INC.");
|
|
+MODULE_DESCRIPTION("I2S driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/sound/soc/eswin/esw-i2s.h b/sound/soc/eswin/esw-i2s.h
|
|
new file mode 100644
|
|
index 000000000000..14c4980c8267
|
|
--- /dev/null
|
|
+++ b/sound/soc/eswin/esw-i2s.h
|
|
@@ -0,0 +1,181 @@
|
|
+/*
|
|
+ * Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com)
|
|
+ *
|
|
+ * This file is licensed under the terms of the GNU General Public
|
|
+ * License version 2. This program is licensed "as is" without any
|
|
+ * warranty of any kind, whether express or implied.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ *
|
|
+ * Copyright (C) 2021 ESWIN, Inc. All rights reserved.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
+ * more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef __I2S_H
|
|
+#define __I2S_H
|
|
+
|
|
+#include <linux/device.h>
|
|
+#include <sound/soc.h>
|
|
+#include <sound/pcm.h>
|
|
+#include <sound/pcm_params.h>
|
|
+#include <sound/soc.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/types.h>
|
|
+#include <sound/dmaengine_pcm.h>
|
|
+#include <sound/designware_i2s.h>
|
|
+
|
|
+
|
|
+/* common register for all channel */
|
|
+#define IER 0x000
|
|
+#define IRER 0x004
|
|
+#define ITER 0x008
|
|
+#define CER 0x00C
|
|
+#define CCR 0x010
|
|
+#define RXFFR 0x014
|
|
+#define TXFFR 0x018
|
|
+
|
|
+/* DMA Control Register Offset */
|
|
+#define DMACR 0x200
|
|
+/* DMA Control Register fields */
|
|
+#define DMAEN_TXBLOCK BIT(17)
|
|
+#define DMAEN_RXBLOCK BIT(16)
|
|
+#define DMAEN_TXCH_3 BIT(11)
|
|
+#define DMAEN_TXCH_2 BIT(10)
|
|
+#define DMAEN_TXCH_1 BIT(9)
|
|
+#define DMAEN_TXCH_0 BIT(8)
|
|
+#define DMAEN_RXCH_3 BIT(3)
|
|
+#define DMAEN_RXCH_2 BIT(2)
|
|
+#define DMAEN_RXCH_1 BIT(1)
|
|
+#define DMAEN_RXCH_0 BIT(0)
|
|
+
|
|
+/* Interrupt status register fields */
|
|
+#define ISR_TXFO BIT(5)
|
|
+#define ISR_TXFE BIT(4)
|
|
+#define ISR_RXFO BIT(1)
|
|
+#define ISR_RXDA BIT(0)
|
|
+
|
|
+/* I2STxRxRegisters for all channels */
|
|
+#define LRBR_LTHR(x) (0x40 * x + 0x020)
|
|
+#define RRBR_RTHR(x) (0x40 * x + 0x024)
|
|
+#define RER(x) (0x40 * x + 0x028)
|
|
+#define TER(x) (0x40 * x + 0x02C)
|
|
+#define RCR(x) (0x40 * x + 0x030)
|
|
+#define TCR(x) (0x40 * x + 0x034)
|
|
+#define ISR(x) (0x40 * x + 0x038)
|
|
+#define IMR(x) (0x40 * x + 0x03C)
|
|
+#define ROR(x) (0x40 * x + 0x040)
|
|
+#define TOR(x) (0x40 * x + 0x044)
|
|
+#define RFCR(x) (0x40 * x + 0x048)
|
|
+#define TFCR(x) (0x40 * x + 0x04C)
|
|
+#define RFF(x) (0x40 * x + 0x050)
|
|
+#define TFF(x) (0x40 * x + 0x054)
|
|
+
|
|
+/* I2SCOMPRegisters */
|
|
+#define I2S_COMP_PARAM_2 0x01F0
|
|
+#define I2S_COMP_PARAM_1 0x01F4
|
|
+#define I2S_COMP_VERSION 0x01F8
|
|
+#define I2S_COMP_TYPE 0x01FC
|
|
+
|
|
+/* I2S DMA registers */
|
|
+#define RXDMA_CH(x) (0x4 * x + 0x204)
|
|
+#define TXDMA_CH(x) (0x4 * x + 0x214)
|
|
+
|
|
+/*
|
|
+ * Component parameter register fields - define the I2S block's
|
|
+ * configuration.
|
|
+ */
|
|
+#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25)
|
|
+#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22)
|
|
+#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19)
|
|
+#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16)
|
|
+#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9)
|
|
+#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7)
|
|
+#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6)
|
|
+#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5)
|
|
+#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4)
|
|
+#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2)
|
|
+#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0)
|
|
+#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10)
|
|
+#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7)
|
|
+#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3)
|
|
+#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0)
|
|
+
|
|
+/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
|
|
+#define COMP_MAX_WORDSIZE (1 << 3)
|
|
+#define COMP_MAX_DATA_WIDTH (1 << 2)
|
|
+#define MAX_CHANNEL_NUM 2
|
|
+#define MIN_CHANNEL_NUM 2
|
|
+#define STEREO 0
|
|
+#define TDM 1
|
|
+
|
|
+#define CCR_SCLKG_POS 0
|
|
+#define CCR_WSS_POS 3
|
|
+
|
|
+enum {
|
|
+ CLOCK_CYCLES_16,
|
|
+ CLOCK_CYCLES_24,
|
|
+ CLOCK_CYCLES_32
|
|
+};
|
|
+
|
|
+enum {
|
|
+ NO_CLOCK_GATING,
|
|
+ GATE_CLOCK_CYCLES_12,
|
|
+ GATE_CLOCK_CYCLES_16,
|
|
+ GATE_CLOCK_CYCLES_20,
|
|
+ GATE_CLOCK_CYCLES_24
|
|
+};
|
|
+
|
|
+enum {
|
|
+ IGNORE_WORD_LENGTH,
|
|
+ RESOLUTION_12_BIT,
|
|
+ RESOLUTION_16_BIT,
|
|
+ RESOLUTION_20_BIT,
|
|
+ RESOLUTION_24_BIT,
|
|
+ RESOLUTION_32_BIT
|
|
+};
|
|
+
|
|
+struct i2s_dev {
|
|
+ void __iomem *i2s_base;
|
|
+ struct clk *clk;
|
|
+ struct device *dev;
|
|
+ unsigned int i2s_reg_comp1;
|
|
+ unsigned int i2s_reg_comp2;
|
|
+ unsigned int capability;
|
|
+ u32 fifo_th;
|
|
+ bool use_pio;
|
|
+ /* data related to DMA transfers b/w i2s and DMAC */
|
|
+ struct snd_dmaengine_dai_dma_data play_dma_data;
|
|
+ struct snd_dmaengine_dai_dma_data capture_dma_data;
|
|
+ struct i2s_clk_config_data config;
|
|
+ struct snd_pcm_substream __rcu *tx_substream;
|
|
+ struct snd_pcm_substream __rcu *rx_substream;
|
|
+ unsigned int (*tx_fn)(struct i2s_dev *dev,
|
|
+ struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
|
|
+ bool *period_elapsed,int type);
|
|
+ unsigned int (*rx_fn)(struct i2s_dev *dev,
|
|
+ struct snd_pcm_runtime *runtime, unsigned int rx_ptr,
|
|
+ bool *period_elapsed,int type);
|
|
+ unsigned int tx_ptr;
|
|
+ unsigned int rx_ptr;
|
|
+ u32 xfer_resolution;
|
|
+ int active;
|
|
+ u32 ccr;
|
|
+ void __iomem *i2s_div_base;
|
|
+ u32 i2s_div_num;
|
|
+ bool playback_active;
|
|
+ bool capture_active;
|
|
+ u32 eswin_plat;
|
|
+};
|
|
+
|
|
+#endif /* __I2S_H */
|
|
--
|
|
2.47.0
|
|
|