summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2014-09-01 16:36:34 +0100
committerMark Brown <broonie@kernel.org>2014-09-01 16:36:34 +0100
commit025b78b809134ae710efca7ccf0a84b927ffb7c4 (patch)
treeb091bbcb46e23bbd932bdc8e7c541503f5b9fa20
parent855675f6e6a65688a7f4cf45b9b5a98cf6c6f5c3 (diff)
parent014fd22ef9c6a7e9536b7e16635714a1a34810a8 (diff)
downloadop-kernel-dev-025b78b809134ae710efca7ccf0a84b927ffb7c4.zip
op-kernel-dev-025b78b809134ae710efca7ccf0a84b927ffb7c4.tar.gz
Merge branch 'topic/fsl' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into asoc-fsl-sai
-rw-r--r--Documentation/devicetree/bindings/regmap/regmap.txt47
-rw-r--r--Documentation/devicetree/bindings/sound/es8328.txt38
-rw-r--r--Documentation/devicetree/bindings/sound/fsl-asoc-card.txt82
-rw-r--r--Documentation/devicetree/bindings/sound/fsl-sai.txt7
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-es8328.txt60
-rw-r--r--Documentation/devicetree/bindings/vendor-prefixes.txt1
-rw-r--r--drivers/base/regmap/regmap-i2c.c2
-rw-r--r--drivers/base/regmap/regmap-spi.c2
-rw-r--r--drivers/base/regmap/regmap.c74
-rw-r--r--sound/soc/codecs/Kconfig13
-rw-r--r--sound/soc/codecs/Makefile6
-rw-r--r--sound/soc/codecs/es8328-i2c.c60
-rw-r--r--sound/soc/codecs/es8328-spi.c49
-rw-r--r--sound/soc/codecs/es8328.c756
-rw-r--r--sound/soc/codecs/es8328.h314
-rw-r--r--sound/soc/fsl/Kconfig29
-rw-r--r--sound/soc/fsl/Makefile4
-rw-r--r--sound/soc/fsl/fsl-asoc-card.c574
-rw-r--r--sound/soc/fsl/fsl_asrc.c6
-rw-r--r--sound/soc/fsl/fsl_esai.c5
-rw-r--r--sound/soc/fsl/fsl_sai.c6
-rw-r--r--sound/soc/fsl/fsl_sai.h1
-rw-r--r--sound/soc/fsl/fsl_spdif.c5
-rw-r--r--sound/soc/fsl/imx-es8328.c232
24 files changed, 2339 insertions, 34 deletions
diff --git a/Documentation/devicetree/bindings/regmap/regmap.txt b/Documentation/devicetree/bindings/regmap/regmap.txt
new file mode 100644
index 0000000..b494f8b
--- /dev/null
+++ b/Documentation/devicetree/bindings/regmap/regmap.txt
@@ -0,0 +1,47 @@
+Device-Tree binding for regmap
+
+The endianness mode of CPU & Device scenarios:
+Index Device Endianness properties
+---------------------------------------------------
+1 BE 'big-endian'
+2 LE 'little-endian'
+
+For one device driver, which will run in different scenarios above
+on different SoCs using the devicetree, we need one way to simplify
+this.
+
+Required properties:
+- {big,little}-endian: these are boolean properties, if absent
+ meaning that the CPU and the Device are in the same endianness mode,
+ these properties are for register values and all the buffers only.
+
+Examples:
+Scenario 1 : CPU in LE mode & device in LE mode.
+dev: dev@40031000 {
+ compatible = "name";
+ reg = <0x40031000 0x1000>;
+ ...
+};
+
+Scenario 2 : CPU in LE mode & device in BE mode.
+dev: dev@40031000 {
+ compatible = "name";
+ reg = <0x40031000 0x1000>;
+ ...
+ big-endian;
+};
+
+Scenario 3 : CPU in BE mode & device in BE mode.
+dev: dev@40031000 {
+ compatible = "name";
+ reg = <0x40031000 0x1000>;
+ ...
+};
+
+Scenario 4 : CPU in BE mode & device in LE mode.
+dev: dev@40031000 {
+ compatible = "name";
+ reg = <0x40031000 0x1000>;
+ ...
+ little-endian;
+};
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 = <&reg_3p3v>;
+ AVDD-supply = <&reg_3p3v>;
+ PVDD-supply = <&reg_3p3v>;
+ HPVDD-supply = <&reg_3p3v>;
+ clocks = <&clks 169>;
+ reg = <0x11>;
+};
diff --git a/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt b/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt
new file mode 100644
index 0000000..a96774c
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt
@@ -0,0 +1,82 @@
+Freescale Generic ASoC Sound Card with ASRC support
+
+The Freescale Generic ASoC Sound Card can be used, ideally, for all Freescale
+SoCs connecting with external CODECs.
+
+The idea of this generic sound card is a bit like ASoC Simple Card. However,
+for Freescale SoCs (especially those released in recent years), most of them
+have ASRC (Documentation/devicetree/bindings/sound/fsl,asrc.txt) inside. And
+this is a specific feature that might be painstakingly controlled and merged
+into the Simple Card.
+
+So having this generic sound card allows all Freescale SoC users to benefit
+from the simplification of a new card support and the capability of the wide
+sample rates support through ASRC.
+
+Note: The card is initially designed for those sound cards who use I2S and
+ PCM DAI formats. However, it'll be also possible to support those non
+ I2S/PCM type sound cards, such as S/PDIF audio and HDMI audio, as long
+ as the driver has been properly upgraded.
+
+
+The compatible list for this generic sound card currently:
+ "fsl,imx-audio-cs42888"
+
+ "fsl,imx-audio-wm8962"
+ (compatible with Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt)
+
+ "fsl,imx-audio-sgtl5000"
+ (compatible with Documentation/devicetree/bindings/sound/imx-audio-sgtl5000.txt)
+
+Required properties:
+
+ - compatible : Contains one of entries in the compatible list.
+
+ - model : The user-visible name of this sound complex
+
+ - audio-cpu : The phandle of an CPU DAI controller
+
+ - audio-codec : The phandle of an 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. There're a few pre-designed board connectors:
+ * Line Out Jack
+ * Line In Jack
+ * Headphone Jack
+ * Mic Jack
+ * Ext Spk
+ * AMIC (stands for Analog Microphone Jack)
+ * DMIC (stands for Digital Microphone Jack)
+
+ Note: The "Mic Jack" and "AMIC" are redundant while
+ coexsiting in order to support the old bindings
+ of wm8962 and sgtl5000.
+
+Optional properties:
+
+ - audio-asrc : The phandle of ASRC. It can be absent if there's no
+ need to add ASRC support via DPCM.
+
+Example:
+sound-cs42888 {
+ compatible = "fsl,imx-audio-cs42888";
+ model = "cs42888-audio";
+ audio-cpu = <&esai>;
+ audio-asrc = <&asrc>;
+ audio-codec = <&cs42888>;
+ audio-routing =
+ "Line Out Jack", "AOUT1L",
+ "Line Out Jack", "AOUT1R",
+ "Line Out Jack", "AOUT2L",
+ "Line Out Jack", "AOUT2R",
+ "Line Out Jack", "AOUT3L",
+ "Line Out Jack", "AOUT3R",
+ "Line Out Jack", "AOUT4L",
+ "Line Out Jack", "AOUT4R",
+ "AIN1L", "Line In Jack",
+ "AIN1R", "Line In Jack",
+ "AIN2L", "Line In Jack",
+ "AIN2R", "Line In Jack";
+};
diff --git a/Documentation/devicetree/bindings/sound/fsl-sai.txt b/Documentation/devicetree/bindings/sound/fsl-sai.txt
index dc9f9c3..06a405e 100644
--- a/Documentation/devicetree/bindings/sound/fsl-sai.txt
+++ b/Documentation/devicetree/bindings/sound/fsl-sai.txt
@@ -18,9 +18,8 @@ Required properties:
- pinctrl-names: Must contain a "default" entry.
- pinctrl-NNN: One property must exist for each entry in pinctrl-names.
See ../pinctrl/pinctrl-bindings.txt for details of the property values.
-- big-endian-regs: If this property is absent, the little endian mode will
- be in use as default, or the big endian mode will be in use for all the
- device registers.
+- big-endian: Boolean property, required if all the FTM_PWM registers
+ are big-endian rather than little-endian.
- big-endian-data: If this property is absent, the little endian mode will
be in use as default, or the big endian mode will be in use for all the
fifo data.
@@ -53,6 +52,6 @@ sai2: sai@40031000 {
dma-names = "tx", "rx";
dmas = <&edma0 0 VF610_EDMA_MUXID0_SAI2_TX>,
<&edma0 0 VF610_EDMA_MUXID0_SAI2_RX>;
- big-endian-regs;
+ big-endian;
big-endian-data;
};
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..07b68ab
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-es8328.txt
@@ -0,0 +1,60 @@
+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 = <&reg_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/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index ac7269f..34cc1bf 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -48,6 +48,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.
diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c
index ca193d1..053150a 100644
--- a/drivers/base/regmap/regmap-i2c.c
+++ b/drivers/base/regmap/regmap-i2c.c
@@ -168,6 +168,8 @@ static struct regmap_bus regmap_i2c = {
.write = regmap_i2c_write,
.gather_write = regmap_i2c_gather_write,
.read = regmap_i2c_read,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
};
static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c
index 0eb3097..53d1148 100644
--- a/drivers/base/regmap/regmap-spi.c
+++ b/drivers/base/regmap/regmap-spi.c
@@ -109,6 +109,8 @@ static struct regmap_bus regmap_spi = {
.async_alloc = regmap_spi_async_alloc,
.read = regmap_spi_read,
.read_flag_mask = 0x80,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
};
/**
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index 78f43fb..01ae4b8 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -15,6 +15,7 @@
#include <linux/export.h>
#include <linux/mutex.h>
#include <linux/err.h>
+#include <linux/of.h>
#include <linux/rbtree.h>
#include <linux/sched.h>
@@ -448,6 +449,66 @@ int regmap_attach_dev(struct device *dev, struct regmap *map,
}
EXPORT_SYMBOL_GPL(regmap_attach_dev);
+static enum regmap_endian regmap_get_reg_endian(const struct regmap_bus *bus,
+ const struct regmap_config *config)
+{
+ enum regmap_endian endian;
+
+ /* Retrieve the endianness specification from the regmap config */
+ endian = config->reg_format_endian;
+
+ /* If the regmap config specified a non-default value, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* Retrieve the endianness specification from the bus config */
+ if (bus && bus->reg_format_endian_default)
+ endian = bus->reg_format_endian_default;
+
+ /* If the bus specified a non-default value, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* Use this if no other value was found */
+ return REGMAP_ENDIAN_BIG;
+}
+
+static enum regmap_endian regmap_get_val_endian(struct device *dev,
+ const struct regmap_bus *bus,
+ const struct regmap_config *config)
+{
+ struct device_node *np = dev->of_node;
+ enum regmap_endian endian;
+
+ /* Retrieve the endianness specification from the regmap config */
+ endian = config->val_format_endian;
+
+ /* If the regmap config specified a non-default value, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* Parse the device's DT node for an endianness specification */
+ if (of_property_read_bool(np, "big-endian"))
+ endian = REGMAP_ENDIAN_BIG;
+ else if (of_property_read_bool(np, "little-endian"))
+ endian = REGMAP_ENDIAN_LITTLE;
+
+ /* If the endianness was specified in DT, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* Retrieve the endianness specification from the bus config */
+ if (bus && bus->val_format_endian_default)
+ endian = bus->val_format_endian_default;
+
+ /* If the bus specified a non-default value, use that */
+ if (endian != REGMAP_ENDIAN_DEFAULT)
+ return endian;
+
+ /* Use this if no other value was found */
+ return REGMAP_ENDIAN_BIG;
+}
+
/**
* regmap_init(): Initialise register map
*
@@ -551,17 +612,8 @@ struct regmap *regmap_init(struct device *dev,
map->reg_read = _regmap_bus_read;
}
- reg_endian = config->reg_format_endian;
- if (reg_endian == REGMAP_ENDIAN_DEFAULT)
- reg_endian = bus->reg_format_endian_default;
- if (reg_endian == REGMAP_ENDIAN_DEFAULT)
- reg_endian = REGMAP_ENDIAN_BIG;
-
- val_endian = config->val_format_endian;
- if (val_endian == REGMAP_ENDIAN_DEFAULT)
- val_endian = bus->val_format_endian_default;
- if (val_endian == REGMAP_ENDIAN_DEFAULT)
- val_endian = REGMAP_ENDIAN_BIG;
+ reg_endian = regmap_get_reg_endian(bus, config);
+ val_endian = regmap_get_val_endian(dev, bus, config);
switch (config->reg_bits + map->reg_shift) {
case 2:
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 8838838e..8bca634 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -57,6 +57,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
@@ -405,6 +407,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 20afe0f..31a8283 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -49,6 +49,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
@@ -220,6 +223,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..7a9f65a
--- /dev/null
+++ b/sound/soc/codecs/es8328.c
@@ -0,0 +1,756 @@
+/*
+ * 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"
+
+#define ES8328_SYSCLK_RATE_1X 11289600
+#define ES8328_SYSCLK_RATE_2X 22579200
+
+/* Run the codec at 22.5792 or 11.2896 MHz to support these rates */
+static struct {
+ int rate;
+ u8 ratio;
+} mclk_ratios[] = {
+ { 8000, 9 },
+ {11025, 7 },
+ {22050, 4 },
+ {44100, 2 },
+};
+
+/* 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_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 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)
+{
+ 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 clk_rate;
+ int i;
+ int reg;
+ u8 ratio;
+
+ 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;
+ }
+
+ /* find master mode MCLK to sampling frequency ratio */
+ ratio = mclk_ratios[0].rate;
+ for (i = 1; i < ARRAY_SIZE(mclk_ratios); i++)
+ if (params_rate(params) <= mclk_ratios[i].rate)
+ ratio = mclk_ratios[i].ratio;
+
+ 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, ratio);
+}
+
+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, with BCLK generated automatically */
+ 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);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, ES8328_CONTROL1,
+ ES8328_CONTROL1_VMIDSEL_MASK |
+ ES8328_CONTROL1_ENREF,
+ 0);
+ 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)
+{
+ struct es8328_priv *es8328;
+ int ret;
+
+ es8328 = snd_soc_codec_get_drvdata(codec);
+
+ es8328_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ clk_disable_unprepare(es8328->clk);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(es8328->supplies),
+ es8328->supplies);
+ if (ret) {
+ dev_err(codec->dev, "unable to disable regulators\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int es8328_resume(struct snd_soc_codec *codec)
+{
+ struct regmap *regmap = dev_get_regmap(codec->dev, NULL);
+ struct es8328_priv *es8328;
+ int ret;
+
+ es8328 = snd_soc_codec_get_drvdata(codec);
+
+ ret = clk_prepare_enable(es8328->clk);
+ if (ret) {
+ dev_err(codec->dev, "unable to enable clock\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;
+ }
+
+ regcache_mark_dirty(regmap);
+ ret = regcache_sync(regmap);
+ if (ret) {
+ dev_err(codec->dev, "unable to sync regcache\n");
+ return ret;
+ }
+
+ 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;
+
+ es8328 = snd_soc_codec_get_drvdata(codec);
+
+ 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;
+ int ret;
+ int i;
+
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ es8328 = devm_kzalloc(dev, sizeof(*es8328), GFP_KERNEL);
+ if (es8328 == NULL)
+ return -ENOMEM;
+
+ es8328->regmap = regmap;
+
+ for (i = 0; i < ARRAY_SIZE(es8328->supplies); i++)
+ es8328->supplies[i].supply = supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(es8328->supplies),
+ es8328->supplies);
+ if (ret) {
+ dev_err(dev, "unable to get regulators\n");
+ return ret;
+ }
+
+ 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..cb36afe
--- /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 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_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
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index f54a8fc..7c1da8e 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -241,6 +241,18 @@ 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
+ 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
@@ -269,6 +281,23 @@ config SND_SOC_IMX_MC13783
select SND_SOC_MC13783
select SND_SOC_IMX_PCM_DMA
+config SND_SOC_FSL_ASOC_CARD
+ tristate "Generic ASoC Sound Card with ASRC support"
+ depends on OF && I2C
+ select SND_SOC_IMX_AUDMUX
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_ESAI
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_SSI
+ select SND_SOC_CS42XX8_I2C
+ select SND_SOC_SGTL5000
+ select SND_SOC_WM8962
+ help
+ ALSA SoC Audio support with ASRC feature for Freescale SoCs that have
+ ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888
+ and SGTL5000.
+ Say Y if you want to add support for Freescale Generic ASoC Sound Card.
+
endif # SND_IMX_SOC
endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 9ff5926..d28dc25 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -11,6 +11,7 @@ snd-soc-p1022-rdk-objs := p1022_rdk.o
obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
# Freescale SSI/DMA/SAI/SPDIF Support
+snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o
snd-soc-fsl-sai-objs := fsl_sai.o
snd-soc-fsl-ssi-y := fsl_ssi.o
@@ -19,6 +20,7 @@ snd-soc-fsl-spdif-objs := fsl_spdif.o
snd-soc-fsl-esai-objs := fsl_esai.o
snd-soc-fsl-utils-objs := fsl_utils.o
snd-soc-fsl-dma-objs := fsl_dma.o
+obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
@@ -50,6 +52,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
@@ -59,6 +62,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/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c
new file mode 100644
index 0000000..007c772
--- /dev/null
+++ b/sound/soc/fsl/fsl-asoc-card.c
@@ -0,0 +1,574 @@
+/*
+ * Freescale Generic ASoC Sound Card driver with ASRC
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <nicoleotsuka@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.
+ */
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "fsl_esai.h"
+#include "fsl_sai.h"
+#include "imx-audmux.h"
+
+#include "../codecs/sgtl5000.h"
+#include "../codecs/wm8962.h"
+
+#define RX 0
+#define TX 1
+
+/* Default DAI format without Master and Slave flag */
+#define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF)
+
+/**
+ * CODEC private data
+ *
+ * @mclk_freq: Clock rate of MCLK
+ * @mclk_id: MCLK (or main clock) id for set_sysclk()
+ * @fll_id: FLL (or secordary clock) id for set_sysclk()
+ * @pll_id: PLL id for set_pll()
+ */
+struct codec_priv {
+ unsigned long mclk_freq;
+ u32 mclk_id;
+ u32 fll_id;
+ u32 pll_id;
+};
+
+/**
+ * CPU private data
+ *
+ * @sysclk_freq[2]: SYSCLK rates for set_sysclk()
+ * @sysclk_dir[2]: SYSCLK directions for set_sysclk()
+ * @sysclk_id[2]: SYSCLK ids for set_sysclk()
+ *
+ * Note: [1] for tx and [0] for rx
+ */
+struct cpu_priv {
+ unsigned long sysclk_freq[2];
+ u32 sysclk_dir[2];
+ u32 sysclk_id[2];
+};
+
+/**
+ * Freescale Generic ASOC card private data
+ *
+ * @dai_link[3]: DAI link structure including normal one and DPCM link
+ * @pdev: platform device pointer
+ * @codec_priv: CODEC private data
+ * @cpu_priv: CPU private data
+ * @card: ASoC card structure
+ * @sample_rate: Current sample rate
+ * @sample_format: Current sample format
+ * @asrc_rate: ASRC sample rate used by Back-Ends
+ * @asrc_format: ASRC sample format used by Back-Ends
+ * @dai_fmt: DAI format between CPU and CODEC
+ * @name: Card name
+ */
+
+struct fsl_asoc_card_priv {
+ struct snd_soc_dai_link dai_link[3];
+ struct platform_device *pdev;
+ struct codec_priv codec_priv;
+ struct cpu_priv cpu_priv;
+ struct snd_soc_card card;
+ u32 sample_rate;
+ u32 sample_format;
+ u32 asrc_rate;
+ u32 asrc_format;
+ u32 dai_fmt;
+ char name[32];
+};
+
+/**
+ * This dapm route map exsits for DPCM link only.
+ * The other routes shall go through Device Tree.
+ */
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+ {"Playback", NULL, "CPU-Playback"},
+ {"ASRC-Capture", NULL, "CPU-Capture"},
+ {"CPU-Capture", NULL, "Capture"},
+};
+
+/* Add all possible widgets into here without being redundant */
+static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_MIC("AMIC", NULL),
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+};
+
+static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct cpu_priv *cpu_priv = &priv->cpu_priv;
+ struct device *dev = rtd->card->dev;
+ int ret;
+
+ priv->sample_rate = params_rate(params);
+ priv->sample_format = params_format(params);
+
+ if (priv->card.set_bias_level)
+ return 0;
+
+ /* Specific configurations of DAIs starts from here */
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, cpu_priv->sysclk_id[tx],
+ cpu_priv->sysclk_freq[tx],
+ cpu_priv->sysclk_dir[tx]);
+ if (ret) {
+ dev_err(dev, "failed to set sysclk for cpu dai\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops fsl_asoc_card_ops = {
+ .hw_params = fsl_asoc_card_hw_params,
+};
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link fsl_asoc_card_dai[] = {
+ /* Default ASoC DAI Link*/
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .ops = &fsl_asoc_card_ops,
+ },
+ /* DPCM Link between Front-End and Back-End (Optional) */
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .dynamic = 1,
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .platform_name = "snd-soc-dummy",
+ .be_hw_params_fixup = be_hw_params_fixup,
+ .ops = &fsl_asoc_card_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ },
+};
+
+static int fsl_asoc_card_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
+ struct codec_priv *codec_priv = &priv->codec_priv;
+ struct device *dev = card->dev;
+ unsigned int pll_out;
+ int ret;
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+ break;
+
+ if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE)
+ pll_out = priv->sample_rate * 384;
+ else
+ pll_out = priv->sample_rate * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id,
+ codec_priv->mclk_id,
+ codec_priv->mclk_freq, pll_out);
+ if (ret) {
+ dev_err(dev, "failed to start FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->fll_id,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+ break;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
+ codec_priv->mclk_freq,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to switch away from FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, 0, 0, 0);
+ if (ret) {
+ dev_err(dev, "failed to stop FLL: %d\n", ret);
+ return ret;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int fsl_asoc_card_audmux_init(struct device_node *np,
+ struct fsl_asoc_card_priv *priv)
+{
+ struct device *dev = &priv->pdev->dev;
+ u32 int_ptcr = 0, ext_ptcr = 0;
+ int int_port, ext_port;
+ int ret;
+
+ ret = of_property_read_u32(np, "mux-int-port", &int_port);
+ if (ret) {
+ dev_err(dev, "mux-int-port missing or invalid\n");
+ return ret;
+ }
+ ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
+ if (ret) {
+ dev_err(dev, "mux-ext-port missing or invalid\n");
+ return ret;
+ }
+
+ /*
+ * The port numbering in the hardware manual starts at 1, while
+ * the AUDMUX API expects it starts at 0.
+ */
+ int_port--;
+ ext_port--;
+
+ /*
+ * Use asynchronous mode (6 wires) for all cases.
+ * If only 4 wires are needed, just set SSI into
+ * synchronous mode and enable 4 PADs in IOMUX.
+ */
+ switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
+ IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
+ IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_RFSDIR |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR |
+ IMX_AUDMUX_V2_PTCR_TFSDIR |
+ IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
+ IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR |
+ IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
+ IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_RFSDIR |
+ IMX_AUDMUX_V2_PTCR_TFSDIR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
+ IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_RFSDIR |
+ IMX_AUDMUX_V2_PTCR_TFSDIR;
+ ext_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
+ IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR |
+ IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
+ IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
+ IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_RFSDIR |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR |
+ IMX_AUDMUX_V2_PTCR_TFSDIR |
+ IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Asynchronous mode can not be set along with RCLKDIR */
+ ret = imx_audmux_v2_configure_port(int_port, 0,
+ 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(int_port, int_ptcr,
+ 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, 0,
+ IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
+ if (ret) {
+ dev_err(dev, "audmux external port setup failed\n");
+ return ret;
+ }
+
+ ret = imx_audmux_v2_configure_port(ext_port, ext_ptcr,
+ IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
+ if (ret) {
+ dev_err(dev, "audmux external port setup failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
+{
+ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
+ struct codec_priv *codec_priv = &priv->codec_priv;
+ struct device *dev = card->dev;
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
+ codec_priv->mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set sysclk in %s\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_asoc_card_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *codec_np, *asrc_np;
+ struct device_node *np = pdev->dev.of_node;
+ struct platform_device *asrc_pdev = NULL;
+ struct platform_device *cpu_pdev;
+ struct fsl_asoc_card_priv *priv;
+ struct i2c_client *codec_dev;
+ struct clk *codec_clk;
+ u32 width;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(np, "audio-cpu", 0);
+ /* Give a chance to old DT binding */
+ if (!cpu_np)
+ cpu_np = of_parse_phandle(np, "ssi-controller", 0);
+ codec_np = of_parse_phandle(np, "audio-codec", 0);
+ if (!cpu_np || !codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find CPU DAI device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ asrc_np = of_parse_phandle(np, "audio-asrc", 0);
+ if (asrc_np)
+ asrc_pdev = of_find_device_by_node(asrc_np);
+
+ /* Get the MCLK rate only, and leave it controlled by CODEC drivers */
+ codec_clk = clk_get(&codec_dev->dev, NULL);
+ if (!IS_ERR(codec_clk)) {
+ priv->codec_priv.mclk_freq = clk_get_rate(codec_clk);
+ clk_put(codec_clk);
+ }
+
+ /* Default sample rate and format, will be updated in hw_params() */
+ priv->sample_rate = 44100;
+ priv->sample_format = SNDRV_PCM_FORMAT_S16_LE;
+
+ /* Assign a default DAI format, and allow each card to overwrite it */
+ priv->dai_fmt = DAI_FMT_BASE;
+
+ /* Diversify the card configurations */
+ if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) {
+ priv->card.set_bias_level = NULL;
+ priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv.mclk_freq;
+ priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq;
+ priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT;
+ priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT;
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) {
+ priv->codec_priv.mclk_id = SGTL5000_SYSCLK;
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) {
+ priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
+ priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK;
+ priv->codec_priv.fll_id = WM8962_SYSCLK_FLL;
+ priv->codec_priv.pll_id = WM8962_FLL;
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+ } else {
+ dev_err(&pdev->dev, "unknown Device Tree compatible\n");
+ return -EINVAL;
+ }
+
+ /* Common settings for corresponding Freescale CPU DAI driver */
+ if (strstr(cpu_np->name, "ssi")) {
+ /* Only SSI needs to configure AUDMUX */
+ ret = fsl_asoc_card_audmux_init(np, priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init audmux\n");
+ goto asrc_fail;
+ }
+ } else if (strstr(cpu_np->name, "esai")) {
+ priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL;
+ priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL;
+ } else if (strstr(cpu_np->name, "sai")) {
+ priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1;
+ priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
+ }
+
+ sprintf(priv->name, "%s-audio", codec_dev->name);
+
+ /* Initialize sound card */
+ priv->pdev = pdev;
+ priv->card.dev = &pdev->dev;
+ priv->card.name = priv->name;
+ priv->card.dai_link = priv->dai_link;
+ priv->card.dapm_routes = audio_map;
+ priv->card.late_probe = fsl_asoc_card_late_probe;
+ priv->card.num_dapm_routes = ARRAY_SIZE(audio_map);
+ priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets);
+
+ memcpy(priv->dai_link, fsl_asoc_card_dai,
+ sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(priv->dai_link));
+
+ /* Normal DAI Link */
+ priv->dai_link[0].cpu_of_node = cpu_np;
+ priv->dai_link[0].codec_of_node = codec_np;
+ priv->dai_link[0].codec_dai_name = codec_dev->name;
+ priv->dai_link[0].platform_of_node = cpu_np;
+ priv->dai_link[0].dai_fmt = priv->dai_fmt;
+ priv->card.num_links = 1;
+
+ if (asrc_pdev) {
+ /* DPCM DAI Links only if ASRC exsits */
+ priv->dai_link[1].cpu_of_node = asrc_np;
+ priv->dai_link[1].platform_of_node = asrc_np;
+ priv->dai_link[2].codec_dai_name = codec_dev->name;
+ priv->dai_link[2].codec_of_node = codec_np;
+ priv->dai_link[2].cpu_of_node = cpu_np;
+ priv->dai_link[2].dai_fmt = priv->dai_fmt;
+ priv->card.num_links = 3;
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
+ &priv->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto asrc_fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto asrc_fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ /* Finish card registering */
+ platform_set_drvdata(pdev, priv);
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret)
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+
+asrc_fail:
+ of_node_put(asrc_np);
+fail:
+ of_node_put(codec_np);
+ of_node_put(cpu_np);
+
+ return ret;
+}
+
+static const struct of_device_id fsl_asoc_card_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-cs42888", },
+ { .compatible = "fsl,imx-audio-sgtl5000", },
+ { .compatible = "fsl,imx-audio-wm8962", },
+ {}
+};
+
+static struct platform_driver fsl_asoc_card_driver = {
+ .probe = fsl_asoc_card_probe,
+ .driver = {
+ .name = "fsl-asoc-card",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = fsl_asoc_card_dt_ids,
+ },
+};
+module_platform_driver(fsl_asoc_card_driver);
+
+MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC");
+MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>");
+MODULE_ALIAS("platform:fsl-asoc-card");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c
index 8221104..3b14531 100644
--- a/sound/soc/fsl/fsl_asrc.c
+++ b/sound/soc/fsl/fsl_asrc.c
@@ -684,7 +684,7 @@ static bool fsl_asrc_writeable_reg(struct device *dev, unsigned int reg)
}
}
-static struct regmap_config fsl_asrc_regmap_config = {
+static const struct regmap_config fsl_asrc_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
@@ -802,10 +802,6 @@ static int fsl_asrc_probe(struct platform_device *pdev)
asrc_priv->paddr = res->start;
- /* Register regmap and let it prepare core clock */
- if (of_property_read_bool(np, "big-endian"))
- fsl_asrc_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
-
asrc_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs,
&fsl_asrc_regmap_config);
if (IS_ERR(asrc_priv->regmap)) {
diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c
index 72d154e..2882fc6 100644
--- a/sound/soc/fsl/fsl_esai.c
+++ b/sound/soc/fsl/fsl_esai.c
@@ -707,7 +707,7 @@ static bool fsl_esai_writeable_reg(struct device *dev, unsigned int reg)
}
}
-static struct regmap_config fsl_esai_regmap_config = {
+static const struct regmap_config fsl_esai_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
@@ -733,9 +733,6 @@ static int fsl_esai_probe(struct platform_device *pdev)
esai_priv->pdev = pdev;
strcpy(esai_priv->name, np->name);
- if (of_property_read_bool(np, "big-endian"))
- fsl_esai_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
-
/* Get the addresses and IRQ */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(&pdev->dev, res);
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c
index 60fe7c7..a6eb784 100644
--- a/sound/soc/fsl/fsl_sai.c
+++ b/sound/soc/fsl/fsl_sai.c
@@ -544,7 +544,7 @@ static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg)
}
}
-static struct regmap_config fsl_sai_regmap_config = {
+static const struct regmap_config fsl_sai_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
@@ -573,10 +573,6 @@ static int fsl_sai_probe(struct platform_device *pdev)
if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx6sx-sai"))
sai->sai_on_imx = true;
- sai->big_endian_regs = of_property_read_bool(np, "big-endian-regs");
- if (sai->big_endian_regs)
- fsl_sai_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
-
sai->big_endian_data = of_property_read_bool(np, "big-endian-data");
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h
index b3d8864..2cded44 100644
--- a/sound/soc/fsl/fsl_sai.h
+++ b/sound/soc/fsl/fsl_sai.h
@@ -132,7 +132,6 @@ struct fsl_sai {
struct clk *bus_clk;
struct clk *mclk_clk[FSL_SAI_MCLK_MAX];
- bool big_endian_regs;
bool big_endian_data;
bool is_dsp_mode;
bool sai_on_imx;
diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c
index 70acfe4..ae4e4088 100644
--- a/sound/soc/fsl/fsl_spdif.c
+++ b/sound/soc/fsl/fsl_spdif.c
@@ -1040,7 +1040,7 @@ static bool fsl_spdif_writeable_reg(struct device *dev, unsigned int reg)
}
}
-static struct regmap_config fsl_spdif_regmap_config = {
+static const struct regmap_config fsl_spdif_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
@@ -1184,9 +1184,6 @@ static int fsl_spdif_probe(struct platform_device *pdev)
memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai));
spdif_priv->cpu_dai_drv.name = spdif_priv->name;
- if (of_property_read_bool(np, "big-endian"))
- fsl_spdif_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
-
/* Get the addresses and IRQ */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(&pdev->dev, res);
diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c
new file mode 100644
index 0000000..653e66d
--- /dev/null
+++ b/sound/soc/fsl/imx-es8328.c
@@ -0,0 +1,232 @@
+/*
+ * 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",
+ .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");
OpenPOWER on IntegriCloud