[alsa-devel] [PATCH v7 0/3] Add ES8328 audio codec
This patchset adds support for the Everest Semi ES8328 audio codec. It also adds support for using the es8328 on IMX boards.
We write a machine driver rather than using simple-card because the machine driver needs to support regulators for the speaker amps and as well as supporting headphone jacks, in addition to the i.MX audmux routing.
Changes since v6: - Minor clarification of AUDMUX in imx-audio-es8328.txt - Use u32 instead of int in imx-es8328.c for int-mux and ext-mux - Bounds checking on mux-int-port and mux-ext-port in imx-es8328.c - Remove NULL checks before calling of_node_put()
Changes since v5: - Removed empty imx_set_frequency function from imx-es8328.c - Move the clock from imx-audio-es8328 to the es8328 codec itself - Add support for 2x (22.5792 MHz) and 1x (11.2896 MHz) source clocks - Restore registers on resume - Document pins for ES8328, including input pins
Changes since v4: - Simplify clock handling and use assigned-clock-parents - Move the codec regulator from the machine driver to the codec - Specify all four regulators, and require their presence - Fix audio recording, such that it enables the PGA
Changes since v3: - Add OF bindings for everest,es8328 - Add back in patch to add everest as a vendor prefix - Give clocks more generic names - Document required clocks in DT bindings document
Changes since v2: - Change deemph to a bool, and update based on sample rate - Replace value enums with regular enums - Break SPI and I2C drivers into their own modules - Rename everest,es8328 to es8328 as the module wasn't getting detected. Because of this, the patch to vendor-prefixes.txt has been dropped.
Changes since v1: - Rename HP/Speaker to OUT1/OUT2 - Use DAPM widgets for startup/shutdown - Add regulator support to machine driver
Sean Cross (3): devicetree: bindings: Add Everest Semicodunctor ASoC: add es8328 codec driver ASoC: fsl: add imx-es8328 machine driver
Documentation/devicetree/bindings/sound/es8328.txt | 38 ++ .../devicetree/bindings/sound/imx-audio-es8328.txt | 61 ++ .../devicetree/bindings/vendor-prefixes.txt | 1 + sound/soc/codecs/Kconfig | 13 + sound/soc/codecs/Makefile | 6 + sound/soc/codecs/es8328-i2c.c | 60 ++ sound/soc/codecs/es8328-spi.c | 49 ++ sound/soc/codecs/es8328.c | 719 +++++++++++++++++++++ sound/soc/codecs/es8328.h | 314 +++++++++ sound/soc/fsl/Kconfig | 14 + sound/soc/fsl/Makefile | 2 + sound/soc/fsl/imx-es8328.c | 233 +++++++ 12 files changed, 1510 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/es8328.txt create mode 100644 Documentation/devicetree/bindings/sound/imx-audio-es8328.txt create mode 100644 sound/soc/codecs/es8328-i2c.c create mode 100644 sound/soc/codecs/es8328-spi.c create mode 100644 sound/soc/codecs/es8328.c create mode 100644 sound/soc/codecs/es8328.h create mode 100644 sound/soc/fsl/imx-es8328.c
Everest Semiconductor makes audio codecs.
Signed-off-by: Sean Cross xobs@kosagi.com --- Documentation/devicetree/bindings/vendor-prefixes.txt | 1 + 1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 4d7f375..2d88c10 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -46,6 +46,7 @@ epfl Ecole Polytechnique Fédérale de Lausanne epson Seiko Epson Corp. est ESTeem Wireless Modems eukrea Eukréa Electromatique +everest Everest Semiconductor Co. Ltd. excito Excito fsl Freescale Semiconductor GEFanuc GE Fanuc Intelligent Platforms Embedded Systems, Inc.
Add a codec driver for the Everest ES8328. It supports two separate audio outputs and two separate audio inputs.
Signed-off-by: Sean Cross xobs@kosagi.com --- Documentation/devicetree/bindings/sound/es8328.txt | 38 ++ sound/soc/codecs/Kconfig | 13 + sound/soc/codecs/Makefile | 6 + sound/soc/codecs/es8328-i2c.c | 60 ++ sound/soc/codecs/es8328-spi.c | 49 ++ sound/soc/codecs/es8328.c | 719 +++++++++++++++++++++ sound/soc/codecs/es8328.h | 314 +++++++++ 7 files changed, 1199 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/es8328.txt create mode 100644 sound/soc/codecs/es8328-i2c.c create mode 100644 sound/soc/codecs/es8328-spi.c create mode 100644 sound/soc/codecs/es8328.c create mode 100644 sound/soc/codecs/es8328.h
diff --git a/Documentation/devicetree/bindings/sound/es8328.txt b/Documentation/devicetree/bindings/sound/es8328.txt new file mode 100644 index 0000000..30ea8a3 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/es8328.txt @@ -0,0 +1,38 @@ +Everest ES8328 audio CODEC + +This device supports both I2C and SPI. + +Required properties: + + - compatible : "everest,es8328" + - DVDD-supply : Regulator providing digital core supply voltage 1.8 - 3.6V + - AVDD-supply : Regulator providing analog supply voltage 3.3V + - PVDD-supply : Regulator providing digital IO supply voltage 1.8 - 3.6V + - IPVDD-supply : Regulator providing analog output voltage 3.3V + - clocks : A 22.5792 or 11.2896 MHz clock + - reg : the I2C address of the device for I2C, the chip select number for SPI + +Pins on the device (for linking into audio routes): + + * LOUT1 + * LOUT2 + * ROUT1 + * ROUT2 + * LINPUT1 + * RINPUT1 + * LINPUT2 + * RINPUT2 + * Mic Bias + + +Example: + +codec: es8328@11 { + compatible = "everest,es8328"; + DVDD-supply = <®_3p3v>; + AVDD-supply = <®_3p3v>; + PVDD-supply = <®_3p3v>; + HPVDD-supply = <®_3p3v>; + clocks = <&clks 169>; + reg = <0x11>; +}; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index cbfa1e1..6a509e3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -56,6 +56,8 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA732X if I2C select SND_SOC_DA9055 if I2C select SND_SOC_BT_SCO + select SND_SOC_ES8328_SPI if SPI_MASTER + select SND_SOC_ES8328_I2C if I2C select SND_SOC_ISABELLE if I2C select SND_SOC_JZ4740_CODEC select SND_SOC_LM4857 if I2C @@ -396,6 +398,17 @@ config SND_SOC_DMIC config SND_SOC_HDMI_CODEC tristate "HDMI stub CODEC"
+config SND_SOC_ES8328 + tristate "Everest Semi ES8328 CODEC" + +config SND_SOC_ES8328_I2C + tristate + select SND_SOC_ES8328 + +config SND_SOC_ES8328_SPI + tristate + select SND_SOC_ES8328 + config SND_SOC_ISABELLE tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index be3377b..66e7859 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -48,6 +48,9 @@ snd-soc-da732x-objs := da732x.o snd-soc-da9055-objs := da9055.o snd-soc-bt-sco-objs := bt-sco.o snd-soc-dmic-objs := dmic.o +snd-soc-es8328-objs := es8328.o +snd-soc-es8328-i2c-objs := es8328-i2c.o +snd-soc-es8328-spi-objs := es8328-spi.o snd-soc-isabelle-objs := isabelle.o snd-soc-jz4740-codec-objs := jz4740.o snd-soc-l3-objs := l3.o @@ -213,6 +216,9 @@ obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o +obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o +obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o +obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o diff --git a/sound/soc/codecs/es8328-i2c.c b/sound/soc/codecs/es8328-i2c.c new file mode 100644 index 0000000..aae410d --- /dev/null +++ b/sound/soc/codecs/es8328-i2c.c @@ -0,0 +1,60 @@ +/* + * es8328-i2c.c -- ES8328 ALSA SoC I2C Audio driver + * + * Copyright 2014 Sutajio Ko-Usagi PTE LTD + * + * Author: Sean Cross xobs@kosagi.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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[] = { + { "everest,es8328", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, es8328_id); + +static const struct of_device_id es8328_of_match[] = { + { .compatible = "everest,es8328", }, + { } +}; +MODULE_DEVICE_TABLE(of, es8328_of_match); + +static int es8328_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + return es8328_probe(&i2c->dev, + devm_regmap_init_i2c(i2c, &es8328_regmap_config)); +} + +static int es8328_i2c_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_codec(&i2c->dev); + return 0; +} + +static struct i2c_driver es8328_i2c_driver = { + .driver = { + .name = "es8328", + .of_match_table = es8328_of_match, + }, + .probe = es8328_i2c_probe, + .remove = es8328_i2c_remove, + .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/es8328-spi.c b/sound/soc/codecs/es8328-spi.c new file mode 100644 index 0000000..8fbd935 --- /dev/null +++ b/sound/soc/codecs/es8328-spi.c @@ -0,0 +1,49 @@ +/* + * es8328.c -- ES8328 ALSA SoC SPI Audio driver + * + * Copyright 2014 Sutajio Ko-Usagi PTE LTD + * + * Author: Sean Cross xobs@kosagi.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> +#include <sound/soc.h> +#include "es8328.h" + +static const struct of_device_id es8328_of_match[] = { + { .compatible = "everest,es8328", }, + { } +}; +MODULE_DEVICE_TABLE(of, es8328_of_match); + +static int es8328_spi_probe(struct spi_device *spi) +{ + return es8328_probe(&spi->dev, + devm_regmap_init_spi(spi, &es8328_regmap_config)); +} + +static int es8328_spi_remove(struct spi_device *spi) +{ + snd_soc_unregister_codec(&spi->dev); + return 0; +} + +static struct spi_driver es8328_spi_driver = { + .driver = { + .name = "es8328", + .of_match_table = es8328_of_match, + }, + .probe = es8328_spi_probe, + .remove = es8328_spi_remove, +}; + +module_spi_driver(es8328_spi_driver); +MODULE_DESCRIPTION("ASoC ES8328 audio CODEC SPI driver"); +MODULE_AUTHOR("Sean Cross xobs@kosagi.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c new file mode 100644 index 0000000..598dcb8 --- /dev/null +++ b/sound/soc/codecs/es8328.c @@ -0,0 +1,719 @@ +/* + * es8328.c -- ES8328 ALSA SoC Audio driver + * + * Copyright 2014 Sutajio Ko-Usagi PTE LTD + * + * Author: Sean Cross xobs@kosagi.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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 "es8328.h" + +/* Run the codec at 22.5792 or 11.2896 MHz to support these rates */ +enum es8328_rate { + ES8328_RATE_8019, + ES8328_RATE_11025, + ES8328_RATE_22050, + ES8328_RATE_44100, +}; + +#define ES8328_SYSCLK_RATE_1X 11289600 +#define ES8328_SYSCLK_RATE_2X 22579200 + +uint8_t es8328_sample_ratios[] = { + [ES8328_RATE_8019] = 0x9, + [ES8328_RATE_11025] = 0x7, + [ES8328_RATE_22050] = 0x4, + [ES8328_RATE_44100] = 0x2, +}; + +/* 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_44100 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_11025) +#define ES8328_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +struct es8328_priv { + struct regmap *regmap; + struct clk *clk; + int playback_fs; + bool deemph; + struct regulator_bulk_data supplies[ES8328_SUPPLY_NUM]; +}; + +/* + * 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(pga_tlv, 0, 300, 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 int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int es8328_set_deemph(struct snd_soc_codec *codec) +{ + struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); + int val, i, best; + + /* + * If we're using deemphasis select the nearest available sample + * rate. + */ + if (es8328->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - es8328->playback_fs) < + abs(deemph_settings[best] - es8328->playback_fs)) + best = i; + } + + val = best << 1; + } else { + val = 0; + } + + dev_dbg(codec->dev, "Set deemphasis %d\n", val); + + return snd_soc_update_bits(codec, ES8328_DACCONTROL6, 0x6, val); +} + +static int es8328_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = es8328->deemph; + return 0; +} + +static int es8328_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); + int deemph = ucontrol->value.enumerated.item[0]; + int ret; + + if (deemph > 1) + return -EINVAL; + + ret = es8328_set_deemph(codec); + if (ret < 0) + return ret; + + es8328->deemph = deemph; + + 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, 6, 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_DACVOL_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), +}; + +/* + * 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_lline_enum); + +/* Left Mixer */ +static const struct snd_kcontrol_new es8328_left_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", ES8328_DACCONTROL17, 8, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", ES8328_DACCONTROL17, 7, 1, 0), + SOC_DAPM_SINGLE("Right Playback Switch", ES8328_DACCONTROL18, 8, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", ES8328_DACCONTROL18, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new es8328_right_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Playback Switch", ES8328_DACCONTROL19, 8, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", ES8328_DACCONTROL19, 7, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", ES8328_DACCONTROL20, 8, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", ES8328_DACCONTROL20, 7, 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_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" }, + + { "Mic Bias", NULL, "Mic Bias Gen" }, + + { "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" }, + + { "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) +{ + return snd_soc_update_bits(dai->codec, ES8328_DACCONTROL3, + ES8328_DACCONTROL3_DACMUTE, + mute ? ES8328_DACCONTROL3_DACMUTE : 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_codec *codec = dai->codec; + struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); + int reg; + int clk_rate; + u8 val; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = ES8328_DACCONTROL2; + + else + reg = ES8328_ADCCONTROL5; + + clk_rate = clk_get_rate(es8328->clk); + + if ((clk_rate != ES8328_SYSCLK_RATE_1X) && + (clk_rate != ES8328_SYSCLK_RATE_2X)) { + dev_err(codec->dev, + "%s: clock is running at %d Hz, not %d or %d Hz\n", + __func__, clk_rate, + ES8328_SYSCLK_RATE_1X, ES8328_SYSCLK_RATE_2X); + return -EINVAL; + } + + switch (params_rate(params)) { + case 8000: + val = es8328_sample_ratios[ES8328_RATE_8019]; + break; + case 11025: + val = es8328_sample_ratios[ES8328_RATE_11025]; + break; + case 22050: + val = es8328_sample_ratios[ES8328_RATE_22050]; + break; + case 44100: + val = es8328_sample_ratios[ES8328_RATE_44100]; + break; + default: + dev_err(codec->dev, "%s: unknown rate %d\n", + __func__, params_rate(params)); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + es8328->playback_fs = params_rate(params); + es8328_set_deemph(codec); + } + + return snd_soc_update_bits(codec, reg, ES8328_RATEMASK, val); +} + +static int es8328_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); + int clk_rate; + u8 mode = ES8328_DACCONTROL1_DACWL_16; + + /* set master/slave audio interface */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBM_CFM) + return -EINVAL; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode |= ES8328_DACCONTROL1_DACFORMAT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + mode |= ES8328_DACCONTROL1_DACFORMAT_RJUST; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode |= ES8328_DACCONTROL1_DACFORMAT_LJUST; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + + snd_soc_write(codec, ES8328_DACCONTROL1, mode); + snd_soc_write(codec, ES8328_ADCCONTROL4, mode); + + /* Master serial port mode */ + snd_soc_write(codec, ES8328_MASTERMODE, ES8328_MASTERMODE_MSC); + + clk_rate = clk_get_rate(es8328->clk); + if (clk_rate == ES8328_SYSCLK_RATE_1X) + snd_soc_write(codec, ES8328_MASTERMODE, + ES8328_MASTERMODE_MSC); + else + snd_soc_write(codec, ES8328_MASTERMODE, + ES8328_MASTERMODE_MCLKDIV2 | + ES8328_MASTERMODE_MSC); + + return 0; +} + +static int es8328_set_bias_level(struct snd_soc_codec *codec, + 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_write(codec, ES8328_CHIPPOWER, 0); + snd_soc_update_bits(codec, ES8328_CONTROL1, + ES8328_CONTROL1_VMIDSEL_MASK | + ES8328_CONTROL1_ENREF, + ES8328_CONTROL1_VMIDSEL_50k | + ES8328_CONTROL1_ENREF); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + snd_soc_update_bits(codec, ES8328_CONTROL1, + ES8328_CONTROL1_VMIDSEL_MASK | + ES8328_CONTROL1_ENREF, + ES8328_CONTROL1_VMIDSEL_5k | + ES8328_CONTROL1_ENREF); + + /* Charge caps */ + msleep(100); + } + + snd_soc_write(codec, ES8328_CONTROL2, + ES8328_CONTROL2_OVERCURRENT_ON | + ES8328_CONTROL2_THERMAL_SHUTDOWN_ON); + + /* VREF, VMID=2*500k, digital stopped */ + snd_soc_update_bits(codec, ES8328_CONTROL1, + ES8328_CONTROL1_VMIDSEL_MASK | + ES8328_CONTROL1_ENREF, + ES8328_CONTROL1_VMIDSEL_500k | + ES8328_CONTROL1_ENREF); + snd_soc_write(codec, ES8328_CHIPPOWER, + ES8328_CHIPPOWER_DACDIG_OFF | + ES8328_CHIPPOWER_ADCDIG_OFF | + ES8328_CHIPPOWER_ADCVREF_OFF | + ES8328_CHIPPOWER_DACVREF_OFF); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, ES8328_CONTROL1, + ES8328_CONTROL1_VMIDSEL_MASK | + ES8328_CONTROL1_ENREF, + 0); + snd_soc_write(codec, ES8328_CHIPPOWER, + ES8328_CHIPPOWER_ADCDIG_OFF | + ES8328_CHIPPOWER_DACDIG_OFF | + ES8328_CHIPPOWER_ADCSTM_RESET | + ES8328_CHIPPOWER_DACSTM_RESET | + ES8328_CHIPPOWER_ADCPLL_OFF | + ES8328_CHIPPOWER_DACPLL_OFF | + ES8328_CHIPPOWER_ADCVREF_OFF | + ES8328_CHIPPOWER_DACVREF_OFF); + break; + } + codec->dapm.bias_level = level; + return 0; +} + +static const struct snd_soc_dai_ops es8328_dai_ops = { + .hw_params = es8328_hw_params, + .digital_mute = es8328_mute, + .set_fmt = es8328_set_dai_fmt, +}; + +static struct snd_soc_dai_driver es8328_dai = { + .name = "es8328-hifi-analog", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ES8328_RATES, + .formats = ES8328_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ES8328_RATES, + .formats = ES8328_FORMATS, + }, + .ops = &es8328_dai_ops, +}; + +static int es8328_suspend(struct snd_soc_codec *codec) +{ + es8328_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int es8328_resume(struct snd_soc_codec *codec) +{ + struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + + regcache_mark_dirty(regmap); + regcache_sync(regmap); + + es8328_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +static int es8328_codec_probe(struct snd_soc_codec *codec) +{ + struct es8328_priv *es8328; + int ret; + int i; + + es8328 = snd_soc_codec_get_drvdata(codec); + + for (i = 0; i < ARRAY_SIZE(es8328->supplies); i++) + es8328->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(codec->dev, ARRAY_SIZE(es8328->supplies), + es8328->supplies); + if (ret) { + dev_err(codec->dev, "unable to get regulators\n"); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(es8328->supplies), + es8328->supplies); + if (ret) { + dev_err(codec->dev, "unable to enable regulators\n"); + return ret; + } + + /* Setup clocks */ + es8328->clk = devm_clk_get(codec->dev, NULL); + if (IS_ERR(es8328->clk)) { + dev_err(codec->dev, "codec clock missing or invalid\n"); + goto clk_fail; + } + + ret = clk_prepare_enable(es8328->clk); + if (ret) { + dev_err(codec->dev, "unable to prepare codec clk\n"); + goto clk_fail; + } + + return 0; + +clk_fail: + regulator_bulk_disable(ARRAY_SIZE(es8328->supplies), + es8328->supplies); + return ret; +} + +static int es8328_remove(struct snd_soc_codec *codec) +{ + struct es8328_priv *es8328; + + es8328 = snd_soc_codec_get_drvdata(codec); + + if (es8328->clk) + clk_disable_unprepare(es8328->clk); + + regulator_bulk_disable(ARRAY_SIZE(es8328->supplies), + es8328->supplies); + + return 0; +} + +const struct regmap_config es8328_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ES8328_REG_MAX, + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(es8328_regmap_config); + +static struct snd_soc_codec_driver es8328_codec_driver = { + .probe = es8328_codec_probe, + .suspend = es8328_suspend, + .resume = es8328_resume, + .remove = es8328_remove, + .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), +}; + +int es8328_probe(struct device *dev, struct regmap *regmap) +{ + struct es8328_priv *es8328; + + 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); + + return snd_soc_register_codec(dev, + &es8328_codec_driver, &es8328_dai, 1); +} +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/es8328.h b/sound/soc/codecs/es8328.h new file mode 100644 index 0000000..2c03c78 --- /dev/null +++ b/sound/soc/codecs/es8328.h @@ -0,0 +1,314 @@ +/* + * es8328.h -- ES8328 ALSA SoC Audio driver + */ + +#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 (7 << 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 (1 << 0) +#define ES8328_CHIPPOWER_ADCVREF_OFF (1 << 1) +#define ES8328_CHIPPOWER_DACPLL_OFF (1 << 2) +#define ES8328_CHIPPOWER_ADCPLL_OFF (1 << 3) +#define ES8328_CHIPPOWER_DACSTM_RESET (1 << 4) +#define ES8328_CHIPPOWER_ADCSTM_RESET (1 << 5) +#define ES8328_CHIPPOWER_DACDIG_OFF (1 << 6) +#define ES8328_CHIPPOWER_ADCDIG_OFF (1 << 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_ADCCONTROL4 0x0c +#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_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_24 (0 << 3) +#define ES8328_DACCONTROL1_DACWL_20 (1 << 3) +#define ES8328_DACCONTROL1_DACWL_18 (2 << 3) +#define ES8328_DACCONTROL1_DACWL_16 (3 << 3) +#define ES8328_DACCONTROL1_DACWL_32 (4 << 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_DACVOL_MAX (0xc0) + +#define ES8328_DACCONTROL4 0x1a +#define ES8328_DACCONTROL5 0x1b + +#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_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_LI2MOVOL (7 << 3) +#define ES8328_DACCONTROL21_LI2MO (1 << 6) +#define ES8328_DACCONTROL21_LD2MO (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_PLL1 0 +#define ES8328_PLL2 1 + +/* clock inputs */ +#define ES8328_MCLK 0 +#define ES8328_PCMCLK 1 + +/* clock divider id's */ +#define ES8328_PCMDIV 0 +#define ES8328_BCLKDIV 1 +#define ES8328_VXCLKDIV 2 + +/* PCM clock dividers */ +#define ES8328_PCM_DIV_1 (0 << 6) +#define ES8328_PCM_DIV_3 (2 << 6) +#define ES8328_PCM_DIV_5_5 (3 << 6) +#define ES8328_PCM_DIV_2 (4 << 6) +#define ES8328_PCM_DIV_4 (5 << 6) +#define ES8328_PCM_DIV_6 (6 << 6) +#define ES8328_PCM_DIV_8 (7 << 6) + +/* BCLK clock dividers */ +#define ES8328_BCLK_DIV_1 (0 << 7) +#define ES8328_BCLK_DIV_2 (1 << 7) +#define ES8328_BCLK_DIV_4 (2 << 7) +#define ES8328_BCLK_DIV_8 (3 << 7) + +/* VXCLK clock dividers */ +#define ES8328_VXCLK_DIV_1 (0 << 6) +#define ES8328_VXCLK_DIV_2 (1 << 6) +#define ES8328_VXCLK_DIV_4 (2 << 6) +#define ES8328_VXCLK_DIV_8 (3 << 6) +#define ES8328_VXCLK_DIV_16 (4 << 6) + +#define ES8328_DAI_HIFI 0 +#define ES8328_DAI_VOICE 1 + +#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
This adds an initial machine driver for the ES8328 audio codec on Freescale boards. The driver supports headphones and an audio regulator for an onboard speaker amp.
Signed-off-by: Sean Cross xobs@kosagi.com --- .../devicetree/bindings/sound/imx-audio-es8328.txt | 61 ++++++ sound/soc/fsl/Kconfig | 14 ++ sound/soc/fsl/Makefile | 2 + sound/soc/fsl/imx-es8328.c | 233 +++++++++++++++++++++ 4 files changed, 310 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/imx-audio-es8328.txt create mode 100644 sound/soc/fsl/imx-es8328.c
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-es8328.txt b/Documentation/devicetree/bindings/sound/imx-audio-es8328.txt new file mode 100644 index 0000000..b89f146 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-es8328.txt @@ -0,0 +1,61 @@ +Freescale i.MX audio complex with ES8328 codec + +Required properties: +- compatible : "fsl,imx-audio-es8328" +- model : The user-visible name of this sound complex +- ssi-controller : The phandle of the i.MX SSI controller +- jack-gpio : Optional GPIO for headphone jack +- audio-amp-supply : Power regulator for speaker amps +- audio-codec : The phandle of the ES8328 audio codec +- audio-routing : A list of the connections between audio components. + Each entry is a pair of strings, the first being the connection's sink, + the second being the connection's source. Valid names could be power + supplies, ES8328 pins, and the jacks on the board: + + Power supplies: + * audio-amp + + ES8328 pins: + * LOUT1 + * LOUT2 + * ROUT1 + * ROUT2 + * LINPUT1 + * LINPUT2 + * RINPUT1 + * RINPUT2 + * Mic PGA + + Board connectors: + * Headphone + * Speaker + * Mic Jack + +- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX) +- mux-ext-port : The external port of the i.MX audio muxer (AUDMIX) + +Note: The AUDMUX port numbering should start at 1, which is consistent with +hardware manual. + +Example: + +sound { + compatible = "fsl,imx-audio-es8328"; + model = "imx-audio-es8328"; + ssi-controller = <&ssi1>; + audio-codec = <&codec>; + jack-gpio = <&gpio5 15 0>; + audio-amp-supply = <®_audio_amp>; + audio-routing = + "Speaker", "LOUT2", + "Speaker", "ROUT2", + "Speaker", "audio-amp", + "Headphone", "ROUT1", + "Headphone", "LOUT1", + "LINPUT1", "Mic Jack", + "RINPUT1", "Mic Jack", + "Mic Jack", "Mic Bias"; + mux-int-port = <1>; + mux-ext-port = <3>; +}; + diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 3793362..c0ace69 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -229,6 +229,20 @@ config SND_SOC_IMX_WM8962 Say Y if you want to add support for SoC audio on an i.MX board with a wm8962 codec.
+config SND_SOC_IMX_ES8328 + tristate "SoC Audio support for i.MX boards with the ES8328 codec" + depends on OF && (I2C || SPI) + select SND_SOC_ES8328_I2C if I2C + select SND_SOC_ES8328_SPI if SPI_MASTER + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_IMX_PCM_FIQ + help + Say Y if you want to add support for the ES8328 audio codec connected + via SSI/I2S over either SPI or I2C. + config SND_SOC_IMX_SGTL5000 tristate "SoC Audio support for i.MX boards with sgtl5000" depends on OF && I2C diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index db254e3..e561766 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -48,6 +48,7 @@ snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o snd-soc-phycore-ac97-objs := phycore-ac97.o snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o +snd-soc-imx-es8328-objs := imx-es8328.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o snd-soc-imx-wm8962-objs := imx-wm8962.o snd-soc-imx-spdif-objs := imx-spdif.o @@ -57,6 +58,7 @@ obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o +obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c new file mode 100644 index 0000000..3c426cd --- /dev/null +++ b/sound/soc/fsl/imx-es8328.c @@ -0,0 +1,233 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 +#define MUX_PORT_MAX 7 + +struct imx_es8328_data { + struct device *dev; + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + int jack_gpio; +}; + +static struct snd_soc_jack_gpio headset_jack_gpios[] = { + { + .gpio = -1, + .name = "headset-gpio", + .report = SND_JACK_HEADSET, + .invert = 0, + .debounce_time = 200, + }, +}; + +static struct snd_soc_jack headset_jack; + +static int imx_es8328_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct imx_es8328_data *data = container_of(rtd->card, + struct imx_es8328_data, card); + int ret = 0; + + /* Headphone jack detection */ + if (gpio_is_valid(data->jack_gpio)) { + ret = snd_soc_jack_new(rtd->codec, "Headphone", + SND_JACK_HEADPHONE | SND_JACK_BTN_0, + &headset_jack); + if (ret) + return ret; + + headset_jack_gpios[0].gpio = data->jack_gpio; + ret = snd_soc_jack_add_gpios(&headset_jack, + ARRAY_SIZE(headset_jack_gpios), + headset_jack_gpios); + } + + return ret; +} + +static const struct snd_soc_dapm_widget imx_es8328_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_REGULATOR_SUPPLY("audio-amp", 1, 0), +}; + +static int imx_es8328_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np, *codec_np; + struct platform_device *ssi_pdev; + struct imx_es8328_data *data; + u32 int_port, ext_port; + int ret; + struct device *dev = &pdev->dev; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(dev, "mux-int-port missing or invalid\n"); + goto fail; + } + if (int_port > MUX_PORT_MAX || int_port == 0) { + dev_err(dev, "mux-int-port: hardware only has %d mux ports\n", + MUX_PORT_MAX); + goto fail; + } + + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(dev, "mux-ext-port missing or invalid\n"); + goto fail; + } + if (ext_port > MUX_PORT_MAX || ext_port == 0) { + dev_err(dev, "mux-ext-port: hardware only has %d mux ports\n", + MUX_PORT_MAX); + goto fail; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(dev, "audmux internal port setup failed\n"); + return ret; + } + ret = imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(dev, "audmux external port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!ssi_np || !codec_np) { + dev_err(dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->dev = dev; + + data->jack_gpio = of_get_named_gpio(pdev->dev.of_node, "jack-gpio", 0); + + data->dai.name = "hifi"; + data->dai.stream_name = "hifi"; + data->dai.codec_dai_name = "es8328-hifi-analog"; + data->dai.codec_of_node = codec_np; + data->dai.cpu_of_node = ssi_np; + data->dai.platform_of_node = ssi_np; + data->dai.init = &imx_es8328_dai_init; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + data->card.dev = dev; + data->card.dapm_widgets = imx_es8328_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_es8328_dapm_widgets); + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(dev, "Unable to parse card name\n"); + goto fail; + } + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) { + dev_err(dev, "Unable to parse routing: %d\n", ret); + goto fail; + } + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + data->card.dai_link = &data->dai; + + ret = snd_soc_register_card(&data->card); + if (ret) { + dev_err(dev, "Unable to register: %d\n", ret); + goto fail; + } + + platform_set_drvdata(pdev, data); +fail: + of_node_put(ssi_np); + of_node_put(codec_np); + + return ret; +} + +static int imx_es8328_remove(struct platform_device *pdev) +{ + struct imx_es8328_data *data = platform_get_drvdata(pdev); + + snd_soc_jack_free_gpios(&headset_jack, ARRAY_SIZE(headset_jack_gpios), + headset_jack_gpios); + + snd_soc_unregister_card(&data->card); + + return 0; +} + +static const struct of_device_id imx_es8328_dt_ids[] = { + { .compatible = "fsl,imx-audio-es8328", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_es8328_dt_ids); + +static struct platform_driver imx_es8328_driver = { + .driver = { + .name = "imx-es8328", + .owner = THIS_MODULE, + .of_match_table = imx_es8328_dt_ids, + }, + .probe = imx_es8328_probe, + .remove = imx_es8328_remove, +}; +module_platform_driver(imx_es8328_driver); + +MODULE_AUTHOR("Sean Cross xobs@kosagi.com"); +MODULE_DESCRIPTION("Kosagi i.MX6 ES8328 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-audio-es8328");
participants (1)
-
Sean Cross