* [PATCH v2 00/14] ASoC: sun4i-codec: Add support for A31 Codec
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
Hi everyone,
This is v2 of my Allwinner A31 audio codec support series. The ASoC DAPM
stereo control patches in v1 were split out and merged already. These
remaining patches depend on them.
The A31's internal codec is similar (in terms of DMA, interface and
control layouts) to the one found in the A10/A13/A20 SoCs. However
it has more external inputs and outputs, the mixer controls are now
stereo (left/right separated), and it also has some audio processing
features (not supported yet).
Changes since v1:
- Patches 1, 2, 3 and 4 were split out from patch 6.
- Reworked handling of register offset differences with regmap_field,
as suggested by Maxime.
- Added support for an optional (in the driver) reset control (patch 5)
- Added required reset control for sun6i-a31-codec
- Added more comments for sun6i registers
- Added HPCOM (direct drive mode) support
- Renamed sun6i audio card name to "A31 Audio Codec"
- Added example for allwinner,sun6i-a31-codec device tree
- Fixed line out volume control scale
- Added external amplifier enable GPIO for Hummingbird A31
- Added patch to enable audio codec on Sinlinx SinA31s
Patch 1 moves some code around to facilitate patch 2. No code was
changed
Patch 2 expands the quirks to handle different register offsets for
- ADC FIFO controls, using regmap_field's
- RX/TX FIFO addresses, with offsets stored directly, as these
are only used to setup DMA and not actually accessed by the
driver
- card setup functions, as the newer codecs have a very different
layout
Patch 3 fixes some of the comments in the register definition section,
so the separation of different functions is clearer.
Patch 4 increases the maximum DMA burst size from 4 to 8. 4 is not
a valid size on later SoCs.
Patch 5 adds driver support for an optional reset control.
Patches 6 through 11 add support for the A31's internal codec, one
feature per patch. Hopefully this makes it easier to review. Some
features, such as PHONE inputs/outputs, audio processing, headset
jack detection and buttons, aren't supported yet.
Patch 12 adds a device node for the codec to the A31 dtsi.
Patch 13 enables the codec for the Hummingbird A31 board.
Patch 14 enables the codec for the Sinlinx SinA31s board.
Please let me know what you think.
Regards
ChenYu
Chen-Yu Tsai (14):
ASoC: sun4i-codec: Move data structures to add create_card call to
quirks
ASoC: sun4i-codec: Expand quirks to handle register offsets and card
creation
ASoC: sun4i-codec: Revise comments for register definition macros
ASoC: sun4i-codec: Increase DMA max burst to 8
ASoC: sun4i-codec: Add support for optional reset control to quirks
ASoC: sun4i-codec: Add support for A31 playback through headphone
output
ASoC: sun4i-codec: Add support for A31 Line In playback
ASoC: sun4i-codec: Add support for A31 Line Out playback
ASoC: sun4i-codec: Add support for A31 analog microphone inputs
ASoC: sun4i-codec: Add support for A31 ADC capture path
ASoC: sun4i-codec: Add support for A31 board level audio routing
ARM: dts: sun6i: Add audio codec device node
ARM: dts: sun6i: hummingbird: Enable internal audio codec
ARM: dts: sun6i: sina31s: Enable internal audio codec
.../devicetree/bindings/sound/sun4i-codec.txt | 55 +-
arch/arm/boot/dts/sun6i-a31-hummingbird.dts | 20 +
arch/arm/boot/dts/sun6i-a31.dtsi | 13 +
arch/arm/boot/dts/sun6i-a31s-sina31s.dts | 8 +
sound/soc/sunxi/sun4i-codec.c | 688 ++++++++++++++++++---
5 files changed, 706 insertions(+), 78 deletions(-)
--
2.10.2
^ permalink raw reply
* [PATCH v2 01/14] ASoC: sun4i-codec: Move data structures to add create_card call to quirks
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The audio codec on later Allwinner SoCs have a different layout and
audio path compared to the A10/A20. However the PCM parts are still
the same.
The different layout and audio paths mean we need a different
create_card function for different families, so they can create
DAPM endpoint widgets and routes.
This patch moves the regmap configs, quirks and of_device_id
structures to just before the probe function, so we can, among other
things, include a pointer for the create_card function. None of the
lines of code were changed.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 78 +++++++++++++++++++++----------------------
1 file changed, 39 insertions(+), 39 deletions(-)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index 56ed9472e89f..249fa5033361 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -678,45 +678,6 @@ static struct snd_soc_dai_driver dummy_cpu_dai = {
},
};
-static const struct regmap_config sun4i_codec_regmap_config = {
- .reg_bits = 32,
- .reg_stride = 4,
- .val_bits = 32,
- .max_register = SUN4I_CODEC_ADC_RXCNT,
-};
-
-static const struct regmap_config sun7i_codec_regmap_config = {
- .reg_bits = 32,
- .reg_stride = 4,
- .val_bits = 32,
- .max_register = SUN7I_CODEC_AC_MIC_PHONE_CAL,
-};
-
-struct sun4i_codec_quirks {
- const struct regmap_config *regmap_config;
-};
-
-static const struct sun4i_codec_quirks sun4i_codec_quirks = {
- .regmap_config = &sun4i_codec_regmap_config,
-};
-
-static const struct sun4i_codec_quirks sun7i_codec_quirks = {
- .regmap_config = &sun7i_codec_regmap_config,
-};
-
-static const struct of_device_id sun4i_codec_of_match[] = {
- {
- .compatible = "allwinner,sun4i-a10-codec",
- .data = &sun4i_codec_quirks,
- },
- {
- .compatible = "allwinner,sun7i-a20-codec",
- .data = &sun7i_codec_quirks,
- },
- {}
-};
-MODULE_DEVICE_TABLE(of, sun4i_codec_of_match);
-
static struct snd_soc_dai_link *sun4i_codec_create_link(struct device *dev,
int *num_links)
{
@@ -781,6 +742,45 @@ static struct snd_soc_card *sun4i_codec_create_card(struct device *dev)
return card;
};
+static const struct regmap_config sun4i_codec_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = SUN4I_CODEC_ADC_RXCNT,
+};
+
+static const struct regmap_config sun7i_codec_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = SUN7I_CODEC_AC_MIC_PHONE_CAL,
+};
+
+struct sun4i_codec_quirks {
+ const struct regmap_config *regmap_config;
+};
+
+static const struct sun4i_codec_quirks sun4i_codec_quirks = {
+ .regmap_config = &sun4i_codec_regmap_config,
+};
+
+static const struct sun4i_codec_quirks sun7i_codec_quirks = {
+ .regmap_config = &sun7i_codec_regmap_config,
+};
+
+static const struct of_device_id sun4i_codec_of_match[] = {
+ {
+ .compatible = "allwinner,sun4i-a10-codec",
+ .data = &sun4i_codec_quirks,
+ },
+ {
+ .compatible = "allwinner,sun7i-a20-codec",
+ .data = &sun7i_codec_quirks,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sun4i_codec_of_match);
+
static int sun4i_codec_probe(struct platform_device *pdev)
{
struct snd_soc_card *card;
--
2.10.2
^ permalink raw reply related
* [PATCH v2 02/14] ASoC: sun4i-codec: Expand quirks to handle register offsets and card creation
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The A31 has a similar codec to the A10/A20. The PCM parts are very
similar, with just different register offsets. The analog paths are
very different. There are more inputs and outputs.
The A31s, A23, and H3 have a similar PCM interface, again with register
offsets slightly rearranged. The analog path controls, while very
similar between them and the A31, have been moved a separate bus which
is accessed through a message box like interface in the PRCM address
range. This would be handled by a separate auxiliary device tied in
through the device tree in its supporting create_card function.
The quirks structure is expanded to include different register offsets
and separate callbacks for creating the ASoC card. The regmap_config,
quirks, and of_device_match tables have been moved to facilitate this.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 87 +++++++++++++++++++++++++++++--------------
1 file changed, 60 insertions(+), 27 deletions(-)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index 249fa5033361..dbb47f255bd8 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -3,6 +3,7 @@
* Copyright 2014 Jon Smirl <jonsmirl@gmail.com>
* Copyright 2015 Maxime Ripard <maxime.ripard@free-electrons.com>
* Copyright 2015 Adam Sampson <ats@offog.org>
+ * Copyright 2016 Chen-Yu Tsai <wens@csie.org>
*
* Based on the Allwinner SDK driver, released under the GPL.
*
@@ -24,8 +25,9 @@
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/of.h>
-#include <linux/of_platform.h>
#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>
@@ -106,6 +108,9 @@ struct sun4i_codec {
struct clk *clk_module;
struct gpio_desc *gpio_pa;
+ /* ADC_FIFOC register is at different offset on different SoCs */
+ struct regmap_field *reg_adc_fifoc;
+
struct snd_dmaengine_dai_dma_data capture_dma_data;
struct snd_dmaengine_dai_dma_data playback_dma_data;
};
@@ -134,16 +139,16 @@ static void sun4i_codec_stop_playback(struct sun4i_codec *scodec)
static void sun4i_codec_start_capture(struct sun4i_codec *scodec)
{
/* Enable ADC DRQ */
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
- BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN),
- BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN));
+ regmap_field_update_bits(scodec->reg_adc_fifoc,
+ BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN),
+ BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN));
}
static void sun4i_codec_stop_capture(struct sun4i_codec *scodec)
{
/* Disable ADC DRQ */
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
- BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), 0);
+ regmap_field_update_bits(scodec->reg_adc_fifoc,
+ BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), 0);
}
static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd,
@@ -186,15 +191,15 @@ static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream,
/* Flush RX FIFO */
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
- BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH),
- BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH));
+ regmap_field_update_bits(scodec->reg_adc_fifoc,
+ BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH),
+ BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH));
/* Set RX FIFO trigger level */
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
- 0xf << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
- 0x7 << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL);
+ regmap_field_update_bits(scodec->reg_adc_fifoc,
+ 0xf << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
+ 0x7 << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL);
/*
* FIXME: Undocumented in the datasheet, but
@@ -213,9 +218,9 @@ static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream,
0x1 << 8);
/* Fill most significant bits with valid data MSB */
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
- BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE),
- BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE));
+ regmap_field_update_bits(scodec->reg_adc_fifoc,
+ BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE),
+ BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE));
return 0;
}
@@ -342,18 +347,19 @@ static int sun4i_codec_hw_params_capture(struct sun4i_codec *scodec,
unsigned int hwrate)
{
/* Set ADC sample rate */
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
- 7 << SUN4I_CODEC_ADC_FIFOC_ADC_FS,
- hwrate << SUN4I_CODEC_ADC_FIFOC_ADC_FS);
+ regmap_field_update_bits(scodec->reg_adc_fifoc,
+ 7 << SUN4I_CODEC_ADC_FIFOC_ADC_FS,
+ hwrate << SUN4I_CODEC_ADC_FIFOC_ADC_FS);
/* Set the number of channels we want to use */
if (params_channels(params) == 1)
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
- BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN),
- BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN));
+ regmap_field_update_bits(scodec->reg_adc_fifoc,
+ BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN),
+ BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN));
else
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC,
- BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), 0);
+ regmap_field_update_bits(scodec->reg_adc_fifoc,
+ BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN),
+ 0);
return 0;
}
@@ -758,14 +764,29 @@ static const struct regmap_config sun7i_codec_regmap_config = {
struct sun4i_codec_quirks {
const struct regmap_config *regmap_config;
+ const struct snd_soc_codec_driver *codec;
+ struct snd_soc_card * (*create_card)(struct device *dev);
+ struct reg_field reg_adc_fifoc; /* used for regmap_field */
+ unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */
+ unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */
};
static const struct sun4i_codec_quirks sun4i_codec_quirks = {
.regmap_config = &sun4i_codec_regmap_config,
+ .codec = &sun4i_codec_codec,
+ .create_card = sun4i_codec_create_card,
+ .reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31),
+ .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
+ .reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA,
};
static const struct sun4i_codec_quirks sun7i_codec_quirks = {
.regmap_config = &sun7i_codec_regmap_config,
+ .codec = &sun4i_codec_codec,
+ .create_card = sun4i_codec_create_card,
+ .reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31),
+ .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
+ .reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA,
};
static const struct of_device_id sun4i_codec_of_match[] = {
@@ -838,6 +859,17 @@ static int sun4i_codec_probe(struct platform_device *pdev)
return ret;
}
+ /* reg_field setup */
+ scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev,
+ scodec->regmap,
+ quirks->reg_adc_fifoc);
+ if (IS_ERR(scodec->reg_adc_fifoc)) {
+ ret = PTR_ERR(scodec->reg_adc_fifoc);
+ dev_err(&pdev->dev, "Failed to create regmap fields: %d\n",
+ ret);
+ return ret;
+ }
+
/* Enable the bus clock */
if (clk_prepare_enable(scodec->clk_apb)) {
dev_err(&pdev->dev, "Failed to enable the APB clock\n");
@@ -845,16 +877,16 @@ static int sun4i_codec_probe(struct platform_device *pdev)
}
/* DMA configuration for TX FIFO */
- scodec->playback_dma_data.addr = res->start + SUN4I_CODEC_DAC_TXDATA;
+ scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata;
scodec->playback_dma_data.maxburst = 4;
scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
/* DMA configuration for RX FIFO */
- scodec->capture_dma_data.addr = res->start + SUN4I_CODEC_ADC_RXDATA;
+ scodec->capture_dma_data.addr = res->start + quirks->reg_adc_rxdata;
scodec->capture_dma_data.maxburst = 4;
scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- ret = snd_soc_register_codec(&pdev->dev, &sun4i_codec_codec,
+ ret = snd_soc_register_codec(&pdev->dev, quirks->codec,
&sun4i_codec_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Failed to register our codec\n");
@@ -875,7 +907,7 @@ static int sun4i_codec_probe(struct platform_device *pdev)
goto err_unregister_codec;
}
- card = sun4i_codec_create_card(&pdev->dev);
+ card = quirks->create_card(&pdev->dev);
if (IS_ERR(card)) {
ret = PTR_ERR(card);
dev_err(&pdev->dev, "Failed to create our card\n");
@@ -926,4 +958,5 @@ MODULE_DESCRIPTION("Allwinner A10 codec driver");
MODULE_AUTHOR("Emilio L?pez <emilio@elopez.com.ar>");
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
MODULE_LICENSE("GPL");
--
2.10.2
^ permalink raw reply related
* [PATCH v2 03/14] ASoC: sun4i-codec: Revise comments for register definition macros
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
This revises existing comments in the register definition macros
section, and adds a few more, so that readers can clearly identify
the types of control registers.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index dbb47f255bd8..61ae502a5061 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -40,7 +40,7 @@
#include <sound/initval.h>
#include <sound/dmaengine_pcm.h>
-/* Codec DAC register offsets and bit fields */
+/* Codec DAC digital controls and FIFO registers */
#define SUN4I_CODEC_DAC_DPC (0x00)
#define SUN4I_CODEC_DAC_DPC_EN_DA (31)
#define SUN4I_CODEC_DAC_DPC_DVOL (12)
@@ -57,6 +57,8 @@
#define SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH (0)
#define SUN4I_CODEC_DAC_FIFOS (0x08)
#define SUN4I_CODEC_DAC_TXDATA (0x0c)
+
+/* Codec DAC side analog signal controls */
#define SUN4I_CODEC_DAC_ACTL (0x10)
#define SUN4I_CODEC_DAC_ACTL_DACAENR (31)
#define SUN4I_CODEC_DAC_ACTL_DACAENL (30)
@@ -71,7 +73,7 @@
#define SUN4I_CODEC_DAC_TUNE (0x14)
#define SUN4I_CODEC_DAC_DEBUG (0x18)
-/* Codec ADC register offsets and bit fields */
+/* Codec ADC digital controls and FIFO registers */
#define SUN4I_CODEC_ADC_FIFOC (0x1c)
#define SUN4I_CODEC_ADC_FIFOC_ADC_FS (29)
#define SUN4I_CODEC_ADC_FIFOC_EN_AD (28)
@@ -83,6 +85,8 @@
#define SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH (0)
#define SUN4I_CODEC_ADC_FIFOS (0x20)
#define SUN4I_CODEC_ADC_RXDATA (0x24)
+
+/* Codec ADC side analog signal controls */
#define SUN4I_CODEC_ADC_ACTL (0x28)
#define SUN4I_CODEC_ADC_ACTL_ADC_R_EN (31)
#define SUN4I_CODEC_ADC_ACTL_ADC_L_EN (30)
@@ -95,10 +99,14 @@
#define SUN4I_CODEC_ADC_ACTL_DDE (3)
#define SUN4I_CODEC_ADC_DEBUG (0x2c)
-/* Other various ADC registers */
+/* FIFO counters */
#define SUN4I_CODEC_DAC_TXCNT (0x30)
#define SUN4I_CODEC_ADC_RXCNT (0x34)
+
+/* Calibration register (sun7i only) */
#define SUN7I_CODEC_AC_DAC_CAL (0x38)
+
+/* Microphone controls (sun7i only) */
#define SUN7I_CODEC_AC_MIC_PHONE_CAL (0x3c)
struct sun4i_codec {
--
2.10.2
^ permalink raw reply related
* [PATCH v2 04/14] ASoC: sun4i-codec: Increase DMA max burst to 8
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
According to the DMA engine API documentation, maxburst denotes the
largest possible size of a single transfer, so as not to overflow
destination FIFOs as explained in this excerpt from dmaengine.h
* @src_maxburst: the maximum number of words (note: words, as in
* units of the src_addr_width member, not bytes) that can be sent
* in one burst to the device. Typically something like half the
* FIFO depth on I/O peripherals so you don't overflow it. This
* may or may not be applicable on memory sources.
* @dst_maxburst: same as src_maxburst but for destination target
* mutatis mutandis.
The TX FIFO is 64 samples deep for stereo, and the RX FIFO is 16
samples deep. So maxburst could be 32 and 8 for TX and RX respectively.
Unfortunately the sunxi DMA controller driver takes maxburst as
the requested burst size, rather than a limit, and returns an error
for unsupported values. The original value was 4, but some later
SoCs do not officially support this burst size.
This patch increases maxburst on the TX side to 8, which is supported
by all variants of the sunxi DMA controller.
Cc: Vinod Koul <vinod.koul@intel.com>
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index 61ae502a5061..d867b96d367b 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -886,12 +886,12 @@ static int sun4i_codec_probe(struct platform_device *pdev)
/* DMA configuration for TX FIFO */
scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata;
- scodec->playback_dma_data.maxburst = 4;
+ scodec->playback_dma_data.maxburst = 8;
scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
/* DMA configuration for RX FIFO */
scodec->capture_dma_data.addr = res->start + quirks->reg_adc_rxdata;
- scodec->capture_dma_data.maxburst = 4;
+ scodec->capture_dma_data.maxburst = 8;
scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
ret = snd_soc_register_codec(&pdev->dev, quirks->codec,
--
2.10.2
^ permalink raw reply related
* [PATCH v2 05/14] ASoC: sun4i-codec: Add support for optional reset control to quirks
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The later Allwinner SoCs have a dedicated reset controller, and
peripherals have dedicated reset controls which need to be deasserted
before the associated peripheral can be used.
Add support for this to the quirks structure and probe/remove functions.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 28 +++++++++++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index d867b96d367b..e502966abf8e 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -30,6 +30,7 @@
#include <linux/of_platform.h>
#include <linux/clk.h>
#include <linux/regmap.h>
+#include <linux/reset.h>
#include <linux/gpio/consumer.h>
#include <sound/core.h>
@@ -114,6 +115,7 @@ struct sun4i_codec {
struct regmap *regmap;
struct clk *clk_apb;
struct clk *clk_module;
+ struct reset_control *rst;
struct gpio_desc *gpio_pa;
/* ADC_FIFOC register is at different offset on different SoCs */
@@ -777,6 +779,7 @@ struct sun4i_codec_quirks {
struct reg_field reg_adc_fifoc; /* used for regmap_field */
unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */
unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */
+ bool has_reset;
};
static const struct sun4i_codec_quirks sun4i_codec_quirks = {
@@ -858,6 +861,14 @@ static int sun4i_codec_probe(struct platform_device *pdev)
return PTR_ERR(scodec->clk_module);
}
+ if (quirks->has_reset) {
+ scodec->rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(scodec->rst)) {
+ dev_err(&pdev->dev, "Failed to get reset control\n");
+ return PTR_ERR(scodec->rst);
+ }
+ };
+
scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa",
GPIOD_OUT_LOW);
if (IS_ERR(scodec->gpio_pa)) {
@@ -884,6 +895,16 @@ static int sun4i_codec_probe(struct platform_device *pdev)
return -EINVAL;
}
+ /* Deassert the reset control */
+ if (scodec->rst) {
+ ret = reset_control_deassert(scodec->rst);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to deassert the reset control\n");
+ goto err_clk_disable;
+ }
+ }
+
/* DMA configuration for TX FIFO */
scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata;
scodec->playback_dma_data.maxburst = 8;
@@ -898,7 +919,7 @@ static int sun4i_codec_probe(struct platform_device *pdev)
&sun4i_codec_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Failed to register our codec\n");
- goto err_clk_disable;
+ goto err_assert_reset;
}
ret = devm_snd_soc_register_component(&pdev->dev,
@@ -935,6 +956,9 @@ static int sun4i_codec_probe(struct platform_device *pdev)
err_unregister_codec:
snd_soc_unregister_codec(&pdev->dev);
+err_assert_reset:
+ if (scodec->rst)
+ reset_control_assert(scodec->rst);
err_clk_disable:
clk_disable_unprepare(scodec->clk_apb);
return ret;
@@ -947,6 +971,8 @@ static int sun4i_codec_remove(struct platform_device *pdev)
snd_soc_unregister_card(card);
snd_soc_unregister_codec(&pdev->dev);
+ if (scodec->rst)
+ reset_control_assert(scodec->rst);
clk_disable_unprepare(scodec->clk_apb);
return 0;
--
2.10.2
^ permalink raw reply related
* [PATCH v2 06/14] ASoC: sun4i-codec: Add support for A31 playback through headphone output
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The A31 has a similar codec to the A10/A20. The PCM parts are very
similar, with different register offsets. The analog paths are very
different. There are more inputs and outputs. The ADC mux has been
replaced with a proper mixer.
This patch adds support for the basic playback path of the A31 codec,
from the DAC to the headphones. Headphone detection, microphone,
signaling, other inputs/outputs and capture will be added later.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
.../devicetree/bindings/sound/sun4i-codec.txt | 22 +-
sound/soc/sunxi/sun4i-codec.c | 271 ++++++++++++++++++++-
2 files changed, 287 insertions(+), 6 deletions(-)
diff --git a/Documentation/devicetree/bindings/sound/sun4i-codec.txt b/Documentation/devicetree/bindings/sound/sun4i-codec.txt
index 0dce690f78f5..bf480e9683a3 100644
--- a/Documentation/devicetree/bindings/sound/sun4i-codec.txt
+++ b/Documentation/devicetree/bindings/sound/sun4i-codec.txt
@@ -1,8 +1,10 @@
* Allwinner A10 Codec
Required properties:
-- compatible: must be either "allwinner,sun4i-a10-codec" or
- "allwinner,sun7i-a20-codec"
+- compatible: must be one of the following compatibles:
+ - "allwinner,sun4i-a10-codec"
+ - "allwinner,sun6i-a31-codec"
+ - "allwinner,sun7i-a20-codec"
- reg: must contain the registers location and length
- interrupts: must contain the codec interrupt
- dmas: DMA channels for tx and rx dma. See the DMA client binding,
@@ -17,6 +19,10 @@ Required properties:
Optional properties:
- allwinner,pa-gpios: gpio to enable external amplifier
+Required properties for the following compatibles:
+ - "allwinner,sun6i-a31-codec"
+- resets: phandle to the reset control for this device
+
Example:
codec: codec at 01c22c00 {
#sound-dai-cells = <0>;
@@ -28,3 +34,15 @@ codec: codec at 01c22c00 {
dmas = <&dma 0 19>, <&dma 0 19>;
dma-names = "rx", "tx";
};
+
+codec: codec at 01c22c00 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun6i-a31-codec";
+ reg = <0x01c22c00 0x98>;
+ interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_APB1_CODEC>, <&ccu CLK_CODEC>;
+ clock-names = "apb", "codec";
+ resets = <&ccu RST_APB1_CODEC>;
+ dmas = <&dma 15>, <&dma 15>;
+ dma-names = "rx", "tx";
+};
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index e502966abf8e..12b9e08a3e64 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -110,6 +110,109 @@
/* Microphone controls (sun7i only) */
#define SUN7I_CODEC_AC_MIC_PHONE_CAL (0x3c)
+/*
+ * sun6i specific registers
+ *
+ * sun6i shares the same digital control and FIFO registers as sun4i,
+ * but only the DAC digital controls are at the same offset. The others
+ * have been moved around to accommodate extra analog controls.
+ */
+
+/* Codec DAC digital controls and FIFO registers */
+#define SUN6I_CODEC_ADC_FIFOC (0x10)
+#define SUN6I_CODEC_ADC_FIFOC_EN_AD (28)
+#define SUN6I_CODEC_ADC_FIFOS (0x14)
+#define SUN6I_CODEC_ADC_RXDATA (0x18)
+
+/* Output mixer and gain controls */
+#define SUN6I_CODEC_OM_DACA_CTRL (0x20)
+#define SUN6I_CODEC_OM_DACA_CTRL_DACAREN (31)
+#define SUN6I_CODEC_OM_DACA_CTRL_DACALEN (30)
+#define SUN6I_CODEC_OM_DACA_CTRL_RMIXEN (29)
+#define SUN6I_CODEC_OM_DACA_CTRL_LMIXEN (28)
+#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC1 (23)
+#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC2 (22)
+#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_PHONE (21)
+#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_PHONEP (20)
+#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR (19)
+#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACR (18)
+#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL (17)
+#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC1 (16)
+#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC2 (15)
+#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_PHONE (14)
+#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_PHONEN (13)
+#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL (12)
+#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACL (11)
+#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR (10)
+#define SUN6I_CODEC_OM_DACA_CTRL_RHPIS (9)
+#define SUN6I_CODEC_OM_DACA_CTRL_LHPIS (8)
+#define SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE (7)
+#define SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE (6)
+#define SUN6I_CODEC_OM_DACA_CTRL_HPVOL (0)
+#define SUN6I_CODEC_OM_PA_CTRL (0x24)
+#define SUN6I_CODEC_OM_PA_CTRL_HPPAEN (31)
+#define SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL (29)
+#define SUN6I_CODEC_OM_PA_CTRL_COMPTEN (28)
+#define SUN6I_CODEC_OM_PA_CTRL_MIC1G (15)
+#define SUN6I_CODEC_OM_PA_CTRL_MIC2G (12)
+#define SUN6I_CODEC_OM_PA_CTRL_LINEING (9)
+#define SUN6I_CODEC_OM_PA_CTRL_PHONEG (6)
+#define SUN6I_CODEC_OM_PA_CTRL_PHONEPG (3)
+#define SUN6I_CODEC_OM_PA_CTRL_PHONENG (0)
+
+/* Microphone, line out and phone out controls */
+#define SUN6I_CODEC_MIC_CTRL (0x28)
+#define SUN6I_CODEC_MIC_CTRL_HBIASEN (31)
+#define SUN6I_CODEC_MIC_CTRL_MBIASEN (30)
+#define SUN6I_CODEC_MIC_CTRL_MIC1AMPEN (28)
+#define SUN6I_CODEC_MIC_CTRL_MIC1BOOST (25)
+#define SUN6I_CODEC_MIC_CTRL_MIC2AMPEN (24)
+#define SUN6I_CODEC_MIC_CTRL_MIC2BOOST (21)
+#define SUN6I_CODEC_MIC_CTRL_MIC2SLT (20)
+#define SUN6I_CODEC_MIC_CTRL_LINEOUTLEN (19)
+#define SUN6I_CODEC_MIC_CTRL_LINEOUTREN (18)
+#define SUN6I_CODEC_MIC_CTRL_LINEOUTLSRC (17)
+#define SUN6I_CODEC_MIC_CTRL_LINEOUTRSRC (16)
+#define SUN6I_CODEC_MIC_CTRL_LINEOUTVC (11)
+#define SUN6I_CODEC_MIC_CTRL_PHONEPREG (8)
+
+/* ADC mixer controls */
+#define SUN6I_CODEC_ADC_ACTL (0x2c)
+#define SUN6I_CODEC_ADC_ACTL_ADCREN (31)
+#define SUN6I_CODEC_ADC_ACTL_ADCLEN (30)
+#define SUN6I_CODEC_ADC_ACTL_ADCRG (27)
+#define SUN6I_CODEC_ADC_ACTL_ADCLG (24)
+#define SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC1 (13)
+#define SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC2 (12)
+#define SUN6I_CODEC_ADC_ACTL_RADCMIX_PHONE (11)
+#define SUN6I_CODEC_ADC_ACTL_RADCMIX_PHONEP (10)
+#define SUN6I_CODEC_ADC_ACTL_RADCMIX_LINEINR (9)
+#define SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXR (8)
+#define SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXL (7)
+#define SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC1 (6)
+#define SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC2 (5)
+#define SUN6I_CODEC_ADC_ACTL_LADCMIX_PHONE (4)
+#define SUN6I_CODEC_ADC_ACTL_LADCMIX_PHONEN (3)
+#define SUN6I_CODEC_ADC_ACTL_LADCMIX_LINEINL (2)
+#define SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXL (1)
+#define SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXR (0)
+
+/* Analog performance tuning controls */
+#define SUN6I_CODEC_ADDA_TUNE (0x30)
+
+/* Calibration controls */
+#define SUN6I_CODEC_CALIBRATION (0x34)
+
+/* FIFO counters */
+#define SUN6I_CODEC_DAC_TXCNT (0x40)
+#define SUN6I_CODEC_ADC_RXCNT (0x44)
+
+/* headset jack detection and button support registers */
+#define SUN6I_CODEC_HMIC_CTL (0x50)
+#define SUN6I_CODEC_HMIC_DATA (0x54)
+
+/* TODO sun6i DAP (Digital Audio Processing) bits */
+
struct sun4i_codec {
struct device *dev;
struct regmap *regmap;
@@ -216,9 +319,14 @@ static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream,
* Allwinner's code mentions that it is related
* related to microphone gain
*/
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_ACTL,
- 0x3 << 25,
- 0x1 << 25);
+ if (of_device_is_compatible(scodec->dev->of_node,
+ "allwinner,sun4i-a10-codec") ||
+ of_device_is_compatible(scodec->dev->of_node,
+ "allwinner,sun7i-a20-codec")) {
+ regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_ACTL,
+ 0x3 << 25,
+ 0x1 << 25);
+ }
if (of_device_is_compatible(scodec->dev->of_node,
"allwinner,sun7i-a20-codec"))
@@ -518,7 +626,7 @@ static struct snd_soc_dai_driver sun4i_codec_dai = {
},
};
-/*** Codec ***/
+/*** sun4i Codec ***/
static const struct snd_kcontrol_new sun4i_codec_pa_mute =
SOC_DAPM_SINGLE("Switch", SUN4I_CODEC_DAC_ACTL,
SUN4I_CODEC_DAC_ACTL_PA_MUTE, 1, 0);
@@ -654,6 +762,122 @@ static struct snd_soc_codec_driver sun4i_codec_codec = {
},
};
+/*** sun6i Codec ***/
+
+/* mixer controls */
+static const struct snd_kcontrol_new sun6i_codec_mixer_controls[] = {
+ SOC_DAPM_DOUBLE("DAC Playback Switch",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACL,
+ SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACR, 1, 0),
+ SOC_DAPM_DOUBLE("DAC Reversed Playback Switch",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR,
+ SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL, 1, 0),
+};
+
+/* headphone controls */
+static const char * const sun6i_codec_hp_src_enum_text[] = {
+ "DAC", "Mixer",
+};
+
+static SOC_ENUM_DOUBLE_DECL(sun6i_codec_hp_src_enum,
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_LHPIS,
+ SUN6I_CODEC_OM_DACA_CTRL_RHPIS,
+ sun6i_codec_hp_src_enum_text);
+
+static const struct snd_kcontrol_new sun6i_codec_hp_src[] = {
+ SOC_DAPM_ENUM("Headphone Source Playback Route",
+ sun6i_codec_hp_src_enum),
+};
+
+/* volume / mute controls */
+static const DECLARE_TLV_DB_SCALE(sun6i_codec_dvol_scale, -7308, 116, 0);
+static const DECLARE_TLV_DB_SCALE(sun6i_codec_hp_vol_scale, -6300, 100, 1);
+
+static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = {
+ SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC,
+ SUN4I_CODEC_DAC_DPC_DVOL, 0x3f, 1,
+ sun6i_codec_dvol_scale),
+ SOC_SINGLE_TLV("Headphone Playback Volume",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_HPVOL, 0x3f, 0,
+ sun6i_codec_hp_vol_scale),
+ SOC_DOUBLE("Headphone Playback Switch",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE,
+ SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets[] = {
+ /* Digital parts of the DACs */
+ SND_SOC_DAPM_SUPPLY("DAC Enable", SUN4I_CODEC_DAC_DPC,
+ SUN4I_CODEC_DAC_DPC_EN_DA, 0,
+ NULL, 0),
+
+ /* Analog parts of the DACs */
+ SND_SOC_DAPM_DAC("Left DAC", "Codec Playback",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_DACALEN, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Codec Playback",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_DACAREN, 0),
+
+ /* Mixers */
+ SOC_MIXER_ARRAY("Left Mixer", SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_LMIXEN, 0,
+ sun6i_codec_mixer_controls),
+ SOC_MIXER_ARRAY("Right Mixer", SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_RMIXEN, 0,
+ sun6i_codec_mixer_controls),
+
+ /* Headphone output path */
+ SND_SOC_DAPM_MUX("Headphone Source Playback Route",
+ SND_SOC_NOPM, 0, 0, sun6i_codec_hp_src),
+ SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN6I_CODEC_OM_PA_CTRL,
+ SUN6I_CODEC_OM_PA_CTRL_HPPAEN, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN6I_CODEC_OM_PA_CTRL,
+ SUN6I_CODEC_OM_PA_CTRL_COMPTEN, 0, NULL, 0),
+ SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN6I_CODEC_OM_PA_CTRL,
+ SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL, 0x3, 0x3, 0),
+ SND_SOC_DAPM_OUTPUT("HP"),
+};
+
+static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = {
+ /* DAC Routes */
+ { "Left DAC", NULL, "DAC Enable" },
+ { "Right DAC", NULL, "DAC Enable" },
+
+ /* Left Mixer Routes */
+ { "Left Mixer", "DAC Playback Switch", "Left DAC" },
+ { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" },
+
+ /* Right Mixer Routes */
+ { "Right Mixer", "DAC Playback Switch", "Right DAC" },
+ { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" },
+
+ /* Headphone Routes */
+ { "Headphone Source Playback Route", "DAC", "Left DAC" },
+ { "Headphone Source Playback Route", "DAC", "Right DAC" },
+ { "Headphone Source Playback Route", "Mixer", "Left Mixer" },
+ { "Headphone Source Playback Route", "Mixer", "Right Mixer" },
+ { "Headphone Amp", NULL, "Headphone Source Playback Route" },
+ { "HP", NULL, "Headphone Amp" },
+ { "HPCOM", NULL, "HPCOM Protection" },
+};
+
+static struct snd_soc_codec_driver sun6i_codec_codec = {
+ .component_driver = {
+ .controls = sun6i_codec_codec_widgets,
+ .num_controls = ARRAY_SIZE(sun6i_codec_codec_widgets),
+ .dapm_widgets = sun6i_codec_codec_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sun6i_codec_codec_dapm_widgets),
+ .dapm_routes = sun6i_codec_codec_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(sun6i_codec_codec_dapm_routes),
+ },
+};
+
static const struct snd_soc_component_driver sun4i_codec_component = {
.name = "sun4i-codec",
};
@@ -758,6 +982,24 @@ static struct snd_soc_card *sun4i_codec_create_card(struct device *dev)
return card;
};
+static struct snd_soc_card *sun6i_codec_create_card(struct device *dev)
+{
+ struct snd_soc_card *card;
+
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return ERR_PTR(-ENOMEM);
+
+ card->dai_link = sun4i_codec_create_link(dev, &card->num_links);
+ if (!card->dai_link)
+ return ERR_PTR(-ENOMEM);
+
+ card->dev = dev;
+ card->name = "A31 Audio Codec";
+
+ return card;
+};
+
static const struct regmap_config sun4i_codec_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
@@ -765,6 +1007,13 @@ static const struct regmap_config sun4i_codec_regmap_config = {
.max_register = SUN4I_CODEC_ADC_RXCNT,
};
+static const struct regmap_config sun6i_codec_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = SUN6I_CODEC_HMIC_DATA,
+};
+
static const struct regmap_config sun7i_codec_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
@@ -791,6 +1040,16 @@ static const struct sun4i_codec_quirks sun4i_codec_quirks = {
.reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA,
};
+static const struct sun4i_codec_quirks sun6i_a31_codec_quirks = {
+ .regmap_config = &sun6i_codec_regmap_config,
+ .codec = &sun6i_codec_codec,
+ .create_card = sun6i_codec_create_card,
+ .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31),
+ .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
+ .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA,
+ .has_reset = true,
+};
+
static const struct sun4i_codec_quirks sun7i_codec_quirks = {
.regmap_config = &sun7i_codec_regmap_config,
.codec = &sun4i_codec_codec,
@@ -806,6 +1065,10 @@ static const struct of_device_id sun4i_codec_of_match[] = {
.data = &sun4i_codec_quirks,
},
{
+ .compatible = "allwinner,sun6i-a31-codec",
+ .data = &sun6i_a31_codec_quirks,
+ },
+ {
.compatible = "allwinner,sun7i-a20-codec",
.data = &sun7i_codec_quirks,
},
--
2.10.2
^ permalink raw reply related
* [PATCH v2 07/14] ASoC: sun4i-codec: Add support for A31 Line In playback
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The A31 integrated codec has a stereo "Line In" input. Add support for
it to the playback paths.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index 12b9e08a3e64..4abac9962165 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -774,6 +774,10 @@ static const struct snd_kcontrol_new sun6i_codec_mixer_controls[] = {
SUN6I_CODEC_OM_DACA_CTRL,
SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR,
SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL, 1, 0),
+ SOC_DAPM_DOUBLE("Line In Playback Switch",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL,
+ SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR, 1, 0),
};
/* headphone controls */
@@ -795,6 +799,8 @@ static const struct snd_kcontrol_new sun6i_codec_hp_src[] = {
/* volume / mute controls */
static const DECLARE_TLV_DB_SCALE(sun6i_codec_dvol_scale, -7308, 116, 0);
static const DECLARE_TLV_DB_SCALE(sun6i_codec_hp_vol_scale, -6300, 100, 1);
+static const DECLARE_TLV_DB_SCALE(sun6i_codec_out_mixer_pregain_scale,
+ -450, 150, 0);
static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = {
SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC,
@@ -808,9 +814,16 @@ static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = {
SUN6I_CODEC_OM_DACA_CTRL,
SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE,
SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE, 1, 0),
+ /* Mixer pre-gains */
+ SOC_SINGLE_TLV("Line In Playback Volume",
+ SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_LINEING,
+ 0x7, 0, sun6i_codec_out_mixer_pregain_scale),
};
static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets[] = {
+ /* Line In */
+ SND_SOC_DAPM_INPUT("LINEIN"),
+
/* Digital parts of the DACs */
SND_SOC_DAPM_SUPPLY("DAC Enable", SUN4I_CODEC_DAC_DPC,
SUN4I_CODEC_DAC_DPC_EN_DA, 0,
@@ -852,10 +865,12 @@ static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = {
/* Left Mixer Routes */
{ "Left Mixer", "DAC Playback Switch", "Left DAC" },
{ "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" },
+ { "Left Mixer", "Line In Playback Switch", "LINEIN" },
/* Right Mixer Routes */
{ "Right Mixer", "DAC Playback Switch", "Right DAC" },
{ "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" },
+ { "Right Mixer", "Line In Playback Switch", "LINEIN" },
/* Headphone Routes */
{ "Headphone Source Playback Route", "DAC", "Left DAC" },
--
2.10.2
^ permalink raw reply related
* [PATCH v2 08/14] ASoC: sun4i-codec: Add support for A31 Line Out playback
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The A31 integrated codec has a second "Line Out" output which does not
include an integrated amplifier in its path. This path does have a
separate volume control.
This patch adds support for the playback path from the DAC to the Line
Out pins.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index 4abac9962165..8ba7804a68ed 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -796,11 +796,31 @@ static const struct snd_kcontrol_new sun6i_codec_hp_src[] = {
sun6i_codec_hp_src_enum),
};
+/* line out controls */
+static const char * const sun6i_codec_lineout_src_enum_text[] = {
+ "Stereo", "Mono Differential",
+};
+
+static SOC_ENUM_DOUBLE_DECL(sun6i_codec_lineout_src_enum,
+ SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_LINEOUTLSRC,
+ SUN6I_CODEC_MIC_CTRL_LINEOUTRSRC,
+ sun6i_codec_lineout_src_enum_text);
+
+static const struct snd_kcontrol_new sun6i_codec_lineout_src[] = {
+ SOC_DAPM_ENUM("Line Out Source Playback Route",
+ sun6i_codec_lineout_src_enum),
+};
+
/* volume / mute controls */
static const DECLARE_TLV_DB_SCALE(sun6i_codec_dvol_scale, -7308, 116, 0);
static const DECLARE_TLV_DB_SCALE(sun6i_codec_hp_vol_scale, -6300, 100, 1);
static const DECLARE_TLV_DB_SCALE(sun6i_codec_out_mixer_pregain_scale,
-450, 150, 0);
+static const DECLARE_TLV_DB_RANGE(sun6i_codec_lineout_vol_scale,
+ 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1),
+ 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0),
+);
static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = {
SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC,
@@ -810,10 +830,18 @@ static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = {
SUN6I_CODEC_OM_DACA_CTRL,
SUN6I_CODEC_OM_DACA_CTRL_HPVOL, 0x3f, 0,
sun6i_codec_hp_vol_scale),
+ SOC_SINGLE_TLV("Line Out Playback Volume",
+ SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_LINEOUTVC, 0x1f, 0,
+ sun6i_codec_lineout_vol_scale),
SOC_DOUBLE("Headphone Playback Switch",
SUN6I_CODEC_OM_DACA_CTRL,
SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE,
SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE, 1, 0),
+ SOC_DOUBLE("Line Out Playback Switch",
+ SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_LINEOUTLEN,
+ SUN6I_CODEC_MIC_CTRL_LINEOUTREN, 1, 0),
/* Mixer pre-gains */
SOC_SINGLE_TLV("Line In Playback Volume",
SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_LINEING,
@@ -855,6 +883,11 @@ static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets[] = {
SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN6I_CODEC_OM_PA_CTRL,
SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL, 0x3, 0x3, 0),
SND_SOC_DAPM_OUTPUT("HP"),
+
+ /* Line Out path */
+ SND_SOC_DAPM_MUX("Line Out Source Playback Route",
+ SND_SOC_NOPM, 0, 0, sun6i_codec_lineout_src),
+ SND_SOC_DAPM_OUTPUT("LINEOUT"),
};
static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = {
@@ -880,6 +913,12 @@ static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = {
{ "Headphone Amp", NULL, "Headphone Source Playback Route" },
{ "HP", NULL, "Headphone Amp" },
{ "HPCOM", NULL, "HPCOM Protection" },
+
+ /* Line Out Routes */
+ { "Line Out Source Playback Route", "Stereo", "Left Mixer" },
+ { "Line Out Source Playback Route", "Stereo", "Right Mixer" },
+ { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" },
+ { "LINEOUT", NULL, "Line Out Source Playback Route" },
};
static struct snd_soc_codec_driver sun6i_codec_codec = {
--
2.10.2
^ permalink raw reply related
* [PATCH v2 09/14] ASoC: sun4i-codec: Add support for A31 analog microphone inputs
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The A31 internal codec has 3 microphone outputs, of which MIC2 and MIC3
are muxed internally. The resulting two microphone inputs have separate
gain controls and mixer inputs.
The codec also has 2 microphone bias pins. HBIAS is specifically for the
headphone jack, which also supports headphone detection and control
buttons. These extra functions are not supported yet. The other, MBIAS,
is for all other analog microphones.
There is also mention of digital microphone support, but documentation
is scarce, and no hardware with it is available.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 70 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index 8ba7804a68ed..a0fedb059f1b 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -778,6 +778,14 @@ static const struct snd_kcontrol_new sun6i_codec_mixer_controls[] = {
SUN6I_CODEC_OM_DACA_CTRL,
SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL,
SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR, 1, 0),
+ SOC_DAPM_DOUBLE("Mic1 Playback Switch",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC1,
+ SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC1, 1, 0),
+ SOC_DAPM_DOUBLE("Mic2 Playback Switch",
+ SUN6I_CODEC_OM_DACA_CTRL,
+ SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC2,
+ SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC2, 1, 0),
};
/* headphone controls */
@@ -796,6 +804,21 @@ static const struct snd_kcontrol_new sun6i_codec_hp_src[] = {
sun6i_codec_hp_src_enum),
};
+/* microphone controls */
+static const char * const sun6i_codec_mic2_src_enum_text[] = {
+ "Mic2", "Mic3",
+};
+
+static SOC_ENUM_SINGLE_DECL(sun6i_codec_mic2_src_enum,
+ SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_MIC2SLT,
+ sun6i_codec_mic2_src_enum_text);
+
+static const struct snd_kcontrol_new sun6i_codec_mic2_src[] = {
+ SOC_DAPM_ENUM("Mic2 Amplifier Source Route",
+ sun6i_codec_mic2_src_enum),
+};
+
/* line out controls */
static const char * const sun6i_codec_lineout_src_enum_text[] = {
"Stereo", "Mono Differential",
@@ -821,6 +844,10 @@ static const DECLARE_TLV_DB_RANGE(sun6i_codec_lineout_vol_scale,
0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1),
2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0),
);
+static const DECLARE_TLV_DB_RANGE(sun6i_codec_mic_gain_scale,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0),
+);
static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = {
SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC,
@@ -846,9 +873,42 @@ static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = {
SOC_SINGLE_TLV("Line In Playback Volume",
SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_LINEING,
0x7, 0, sun6i_codec_out_mixer_pregain_scale),
+ SOC_SINGLE_TLV("Mic1 Playback Volume",
+ SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_MIC1G,
+ 0x7, 0, sun6i_codec_out_mixer_pregain_scale),
+ SOC_SINGLE_TLV("Mic2 Playback Volume",
+ SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_MIC2G,
+ 0x7, 0, sun6i_codec_out_mixer_pregain_scale),
+
+ /* Microphone Amp boost gains */
+ SOC_SINGLE_TLV("Mic1 Boost Volume", SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_MIC1BOOST, 0x7, 0,
+ sun6i_codec_mic_gain_scale),
+ SOC_SINGLE_TLV("Mic2 Boost Volume", SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_MIC2BOOST, 0x7, 0,
+ sun6i_codec_mic_gain_scale),
};
static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets[] = {
+ /* Microphone inputs */
+ SND_SOC_DAPM_INPUT("MIC1"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+ SND_SOC_DAPM_INPUT("MIC3"),
+
+ /* Microphone Bias */
+ SND_SOC_DAPM_SUPPLY("HBIAS", SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_HBIASEN, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("MBIAS", SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_MBIASEN, 0, NULL, 0),
+
+ /* Mic input path */
+ SND_SOC_DAPM_MUX("Mic2 Amplifier Source Route",
+ SND_SOC_NOPM, 0, 0, sun6i_codec_mic2_src),
+ SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_MIC1AMPEN, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN6I_CODEC_MIC_CTRL,
+ SUN6I_CODEC_MIC_CTRL_MIC2AMPEN, 0, NULL, 0),
+
/* Line In */
SND_SOC_DAPM_INPUT("LINEIN"),
@@ -895,15 +955,25 @@ static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = {
{ "Left DAC", NULL, "DAC Enable" },
{ "Right DAC", NULL, "DAC Enable" },
+ /* Microphone Routes */
+ { "Mic1 Amplifier", NULL, "MIC1"},
+ { "Mic2 Amplifier Source Route", "Mic2", "MIC2" },
+ { "Mic2 Amplifier Source Route", "Mic3", "MIC3" },
+ { "Mic2 Amplifier", NULL, "Mic2 Amplifier Source Route"},
+
/* Left Mixer Routes */
{ "Left Mixer", "DAC Playback Switch", "Left DAC" },
{ "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" },
{ "Left Mixer", "Line In Playback Switch", "LINEIN" },
+ { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
+ { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
/* Right Mixer Routes */
{ "Right Mixer", "DAC Playback Switch", "Right DAC" },
{ "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" },
{ "Right Mixer", "Line In Playback Switch", "LINEIN" },
+ { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
+ { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
/* Headphone Routes */
{ "Headphone Source Playback Route", "DAC", "Left DAC" },
--
2.10.2
^ permalink raw reply related
* [PATCH v2 10/14] ASoC: sun4i-codec: Add support for A31 ADC capture path
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The A31's internal codec capture path has a mixer in front of the ADC
for each channel, capable of selecting various inputs, including
microphones, line in, phone in, and the main output mixer.
This patch adds the various controls, widgets and routes needed for
audio capture from the already supported inputs on the A31.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
sound/soc/sunxi/sun4i-codec.c | 65 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index a0fedb059f1b..0cb728964ff0 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -788,6 +788,30 @@ static const struct snd_kcontrol_new sun6i_codec_mixer_controls[] = {
SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC2, 1, 0),
};
+/* ADC mixer controls */
+static const struct snd_kcontrol_new sun6i_codec_adc_mixer_controls[] = {
+ SOC_DAPM_DOUBLE("Mixer Capture Switch",
+ SUN6I_CODEC_ADC_ACTL,
+ SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXL,
+ SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXR, 1, 0),
+ SOC_DAPM_DOUBLE("Mixer Reversed Capture Switch",
+ SUN6I_CODEC_ADC_ACTL,
+ SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXR,
+ SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXL, 1, 0),
+ SOC_DAPM_DOUBLE("Line In Capture Switch",
+ SUN6I_CODEC_ADC_ACTL,
+ SUN6I_CODEC_ADC_ACTL_LADCMIX_LINEINL,
+ SUN6I_CODEC_ADC_ACTL_RADCMIX_LINEINR, 1, 0),
+ SOC_DAPM_DOUBLE("Mic1 Capture Switch",
+ SUN6I_CODEC_ADC_ACTL,
+ SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC1,
+ SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC1, 1, 0),
+ SOC_DAPM_DOUBLE("Mic2 Capture Switch",
+ SUN6I_CODEC_ADC_ACTL,
+ SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC2,
+ SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC2, 1, 0),
+};
+
/* headphone controls */
static const char * const sun6i_codec_hp_src_enum_text[] = {
"DAC", "Mixer",
@@ -887,6 +911,10 @@ static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = {
SOC_SINGLE_TLV("Mic2 Boost Volume", SUN6I_CODEC_MIC_CTRL,
SUN6I_CODEC_MIC_CTRL_MIC2BOOST, 0x7, 0,
sun6i_codec_mic_gain_scale),
+ SOC_DOUBLE_TLV("ADC Capture Volume",
+ SUN6I_CODEC_ADC_ACTL, SUN6I_CODEC_ADC_ACTL_ADCLG,
+ SUN6I_CODEC_ADC_ACTL_ADCRG, 0x7, 0,
+ sun6i_codec_out_mixer_pregain_scale),
};
static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets[] = {
@@ -912,6 +940,23 @@ static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets[] = {
/* Line In */
SND_SOC_DAPM_INPUT("LINEIN"),
+ /* Digital parts of the ADCs */
+ SND_SOC_DAPM_SUPPLY("ADC Enable", SUN6I_CODEC_ADC_FIFOC,
+ SUN6I_CODEC_ADC_FIFOC_EN_AD, 0,
+ NULL, 0),
+
+ /* Analog parts of the ADCs */
+ SND_SOC_DAPM_ADC("Left ADC", "Codec Capture", SUN6I_CODEC_ADC_ACTL,
+ SUN6I_CODEC_ADC_ACTL_ADCLEN, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Codec Capture", SUN6I_CODEC_ADC_ACTL,
+ SUN6I_CODEC_ADC_ACTL_ADCREN, 0),
+
+ /* ADC Mixers */
+ SOC_MIXER_ARRAY("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+ sun6i_codec_adc_mixer_controls),
+ SOC_MIXER_ARRAY("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+ sun6i_codec_adc_mixer_controls),
+
/* Digital parts of the DACs */
SND_SOC_DAPM_SUPPLY("DAC Enable", SUN4I_CODEC_DAC_DPC,
SUN4I_CODEC_DAC_DPC_EN_DA, 0,
@@ -975,6 +1020,20 @@ static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = {
{ "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
{ "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
+ /* Left ADC Mixer Routes */
+ { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" },
+ { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" },
+ { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" },
+ { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
+ { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
+
+ /* Right ADC Mixer Routes */
+ { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" },
+ { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" },
+ { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" },
+ { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
+ { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
+
/* Headphone Routes */
{ "Headphone Source Playback Route", "DAC", "Left DAC" },
{ "Headphone Source Playback Route", "DAC", "Right DAC" },
@@ -989,6 +1048,12 @@ static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = {
{ "Line Out Source Playback Route", "Stereo", "Right Mixer" },
{ "Line Out Source Playback Route", "Mono Differential", "Left Mixer" },
{ "LINEOUT", NULL, "Line Out Source Playback Route" },
+
+ /* ADC Routes */
+ { "Left ADC", NULL, "ADC Enable" },
+ { "Right ADC", NULL, "ADC Enable" },
+ { "Left ADC", NULL, "Left ADC Mixer" },
+ { "Right ADC", NULL, "Right ADC Mixer" },
};
static struct snd_soc_codec_driver sun6i_codec_codec = {
--
2.10.2
^ permalink raw reply related
* [PATCH v2 11/14] ASoC: sun4i-codec: Add support for A31 board level audio routing
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The A31 SoC's codec has various inputs, outputs and microphone bias
supplies. These can be routed on the board in different ways, such as:
- HPCOM may be connected to have the headphone DC coupled.
- Microphones all use the MBIAS main microphone supply or one mic may
use the HBIAS supply, which supports headset detection and buttons.
- Line Out may be routed to an audio jack, or an onboard speaker amp
with power controls.
Add support for specifying the audio routes in the device tree.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
.../devicetree/bindings/sound/sun4i-codec.txt | 33 ++++++++++++++++++++++
sound/soc/sunxi/sun4i-codec.c | 21 ++++++++++++--
2 files changed, 52 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/sound/sun4i-codec.txt b/Documentation/devicetree/bindings/sound/sun4i-codec.txt
index bf480e9683a3..d91a95377f49 100644
--- a/Documentation/devicetree/bindings/sound/sun4i-codec.txt
+++ b/Documentation/devicetree/bindings/sound/sun4i-codec.txt
@@ -22,6 +22,31 @@ Optional properties:
Required properties for the following compatibles:
- "allwinner,sun6i-a31-codec"
- resets: phandle to the reset control for this device
+- allwinner,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 include:
+
+ Audio pins on the SoC:
+ "HP"
+ "HPCOM"
+ "LINEIN"
+ "LINEOUT"
+ "MIC1"
+ "MIC2"
+ "MIC3"
+
+ Microphone biases from the SoC:
+ "HBIAS"
+ "MBIAS"
+
+ Board connectors:
+ "Headphone"
+ "Headset Mic"
+ "Line In"
+ "Line Out"
+ "Mic"
+ "Speaker"
Example:
codec: codec at 01c22c00 {
@@ -45,4 +70,12 @@ codec: codec at 01c22c00 {
resets = <&ccu RST_APB1_CODEC>;
dmas = <&dma 15>, <&dma 15>;
dma-names = "rx", "tx";
+ allwinner,audio-routing =
+ "Headphone", "HP",
+ "Speaker", "LINEOUT",
+ "LINEIN", "Line In",
+ "MIC1", "MBIAS",
+ "MIC1", "Mic",
+ "MIC2", "HBIAS",
+ "MIC2", "Headset Mic";
};
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index 0cb728964ff0..6379efd21f00 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -1171,9 +1171,19 @@ static struct snd_soc_card *sun4i_codec_create_card(struct device *dev)
return card;
};
+static const struct snd_soc_dapm_widget sun6i_codec_card_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+ SND_SOC_DAPM_SPK("Speaker", sun4i_codec_spk_event),
+};
+
static struct snd_soc_card *sun6i_codec_create_card(struct device *dev)
{
struct snd_soc_card *card;
+ int ret;
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
if (!card)
@@ -1183,8 +1193,15 @@ static struct snd_soc_card *sun6i_codec_create_card(struct device *dev)
if (!card->dai_link)
return ERR_PTR(-ENOMEM);
- card->dev = dev;
- card->name = "A31 Audio Codec";
+ card->dev = dev;
+ card->name = "A31 Audio Codec";
+ card->dapm_widgets = sun6i_codec_card_dapm_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets);
+ card->fully_routed = true;
+
+ ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing");
+ if (ret)
+ dev_warn(dev, "failed to parse audio-routing: %d\n", ret);
return card;
};
--
2.10.2
^ permalink raw reply related
* [PATCH v2 12/14] ARM: dts: sun6i: Add audio codec device node
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The A31 SoC includes the Allwinner audio codec, capable of 24-bit
playback up to 192 kHz and 24-bit capture up to 48 kHz.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
arch/arm/boot/dts/sun6i-a31.dtsi | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm/boot/dts/sun6i-a31.dtsi b/arch/arm/boot/dts/sun6i-a31.dtsi
index 2e8bf93dcfb2..f68e6102b01b 100644
--- a/arch/arm/boot/dts/sun6i-a31.dtsi
+++ b/arch/arm/boot/dts/sun6i-a31.dtsi
@@ -784,6 +784,19 @@
reset-names = "ahb";
};
+ codec: codec at 01c22c00 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun6i-a31-codec";
+ reg = <0x01c22c00 0x98>;
+ interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_APB1_CODEC>, <&ccu CLK_CODEC>;
+ clock-names = "apb", "codec";
+ resets = <&ccu RST_APB1_CODEC>;
+ dmas = <&dma 15>, <&dma 15>;
+ dma-names = "rx", "tx";
+ status = "disabled";
+ };
+
timer at 01c60000 {
compatible = "allwinner,sun6i-a31-hstimer",
"allwinner,sun7i-a20-hstimer";
--
2.10.2
^ permalink raw reply related
* [PATCH v2 13/14] ARM: dts: sun6i: hummingbird: Enable internal audio codec
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The Hummingbird A31 has headset and line in audio jacks and an onboard
mic routed to the pins for the SoC's internal codec. The line out pins
are routed to an onboard speaker amp, whose output is available on a
pin header.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
arch/arm/boot/dts/sun6i-a31-hummingbird.dts | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/arch/arm/boot/dts/sun6i-a31-hummingbird.dts b/arch/arm/boot/dts/sun6i-a31-hummingbird.dts
index 9a74637f677f..48c041b75aab 100644
--- a/arch/arm/boot/dts/sun6i-a31-hummingbird.dts
+++ b/arch/arm/boot/dts/sun6i-a31-hummingbird.dts
@@ -69,6 +69,19 @@
};
};
+&codec {
+ allwinner,audio-routing =
+ "Headphone", "HP",
+ "Speaker", "LINEOUT",
+ "LINEIN", "Line In",
+ "MIC1", "Mic",
+ "MIC2", "Headset Mic",
+ "Mic", "MBIAS",
+ "Headset Mic", "HBIAS";
+ allwinner,pa-gpios = <&pio 7 22 GPIO_ACTIVE_HIGH>; /* PH22 */
+ status = "okay";
+};
+
&cpu0 {
cpu-supply = <®_dcdc3>;
};
@@ -152,6 +165,13 @@
};
&pio {
+ codec_pa_pin: codec_pa_pin at 0 {
+ allwinner,pins = "PH22";
+ allwinner,function = "gpio_out";
+ allwinner,drive = <SUN4I_PINCTRL_10_MA>;
+ allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
+ };
+
gmac_phy_reset_pin_hummingbird: gmac_phy_reset_pin at 0 {
allwinner,pins = "PA21";
allwinner,function = "gpio_out";
--
2.10.2
^ permalink raw reply related
* [PATCH v2 14/14] ARM: dts: sun6i: sina31s: Enable internal audio codec
From: Chen-Yu Tsai @ 2016-11-03 7:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-1-wens@csie.org>
The SinA31s routes the SoC's LINEOUT pins to a line out jack, and MIC1
to a microphone jack, with MBIAS providing phantom power.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
arch/arm/boot/dts/sun6i-a31s-sina31s.dts | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm/boot/dts/sun6i-a31s-sina31s.dts b/arch/arm/boot/dts/sun6i-a31s-sina31s.dts
index 6ead2f5c847a..c35ec112f5a0 100644
--- a/arch/arm/boot/dts/sun6i-a31s-sina31s.dts
+++ b/arch/arm/boot/dts/sun6i-a31s-sina31s.dts
@@ -65,6 +65,14 @@
};
};
+&codec {
+ allwinner,audio-routing =
+ "Line Out", "LINEOUT",
+ "MIC1", "Mic",
+ "Mic", "MBIAS";
+ status = "okay";
+};
+
&ehci0 {
/* USB 2.0 4 port hub IC */
status = "okay";
--
2.10.2
^ permalink raw reply related
* [PATCH v6 1/4] clk: sunxi-ng: Add A64 clocks
From: Maxime Ripard @ 2016-11-03 8:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAGb2v676Ddz1wmRwDGwTd8gGH5AJJBkyAe-WyvZ=D+2OKoRb4w@mail.gmail.com>
On Thu, Nov 03, 2016 at 09:52:26AM +0800, Chen-Yu Tsai wrote:
> On Thu, Nov 3, 2016 at 5:50 AM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
> > Add the A64 CCU clocks set.
> >
> > Acked-by: Rob Herring <robh@kernel.org>
> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>
> I thought I acked this one...
>
> Skimming through it again, I think you would want to add CLK_SET_PARENT_RATE
> to the hdmi and mipi dsi module clocks.
>
> Otherwise,
>
> Acked-by: Chen-Yu Tsai <wens@csie.org>
I added the flags, and applied, thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161103/afe20e47/attachment-0001.sig>
^ permalink raw reply
* [PATCH v6 4/4] arm64: dts: add Pine64 support
From: Maxime Ripard @ 2016-11-03 8:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <83b8ff39-91f0-4e5c-61a5-e21f31c909ff@arm.com>
On Wed, Nov 02, 2016 at 10:05:09PM +0000, Andr? Przywara wrote:
> On 02/11/16 21:50, Maxime Ripard wrote:
> > From: Andre Przywara <andre.przywara@arm.com>
> >
> > The Pine64 is a cost-efficient development board based on the
> > Allwinner A64 SoC.
> > There are three models: the basic version with Fast Ethernet and
> > 512 MB of DRAM (Pine64) and two Pine64+ versions, which both
> > feature Gigabit Ethernet and additional connectors for touchscreens
> > and a camera. Or as my son put it: "Those are smaller and these are
> > missing." ;-)
> > The two Pine64+ models just differ in the amount of DRAM
> > (1GB vs. 2GB). Since U-Boot will figure out the right size for us and
> > patches the DT accordingly we just need to provide one DT for the
> > Pine64+.
> >
> > Signed-off-by: Andre Przywara <andre.przywara@arm.com>
> > [Maxime: Removed the common DTSI and include directly the pine64 DTS]
> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> > ---
> > arch/arm64/boot/dts/Makefile | 1 +-
> > arch/arm64/boot/dts/allwinner/Makefile | 5 +-
> > arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-plus.dts | 50 ++++++-
> > arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts | 74 +++++++++-
> > 4 files changed, 130 insertions(+), 0 deletions(-)
> > create mode 100644 arch/arm64/boot/dts/allwinner/Makefile
> > create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-plus.dts
> > create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
> >
> > diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile
> > index 6684f97c2722..080232b0270e 100644
> > --- a/arch/arm64/boot/dts/Makefile
> > +++ b/arch/arm64/boot/dts/Makefile
> > @@ -1,4 +1,5 @@
> > dts-dirs += al
> > +dts-dirs += allwinner
> > dts-dirs += altera
> > dts-dirs += amd
> > dts-dirs += amlogic
> > diff --git a/arch/arm64/boot/dts/allwinner/Makefile b/arch/arm64/boot/dts/allwinner/Makefile
> > new file mode 100644
> > index 000000000000..1e29a5ae8282
> > --- /dev/null
> > +++ b/arch/arm64/boot/dts/allwinner/Makefile
> > @@ -0,0 +1,5 @@
> > +dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a64-pine64-plus.dtb sun50i-a64-pine64.dtb
> > +
> > +always := $(dtb-y)
> > +subdir-y := $(dts-dirs)
> > +clean-files := *.dtb
> > diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-plus.dts b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-plus.dts
> > new file mode 100644
> > index 000000000000..790d14daaa6a
> > --- /dev/null
> > +++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-plus.dts
> > @@ -0,0 +1,50 @@
> > +/*
> > + * Copyright (c) 2016 ARM Ltd.
> > + *
> > + * This file is dual-licensed: you can use it either under the terms
> > + * of the GPL or the X11 license, at your option. Note that this dual
> > + * licensing only applies to this file, and not this project as a
> > + * whole.
> > + *
> > + * a) This library is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License as
> > + * published by the Free Software Foundation; either version 2 of the
> > + * License, or (at your option) any later version.
> > + *
> > + * This library is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> > + * GNU General Public License for more details.
> > + *
> > + * Or, alternatively,
> > + *
> > + * b) Permission is hereby granted, free of charge, to any person
> > + * obtaining a copy of this software and associated documentation
> > + * files (the "Software"), to deal in the Software without
> > + * restriction, including without limitation the rights to use,
> > + * copy, modify, merge, publish, distribute, sublicense, and/or
> > + * sell copies of the Software, and to permit persons to whom the
> > + * Software is furnished to do so, subject to the following
> > + * conditions:
> > + *
> > + * The above copyright notice and this permission notice shall be
> > + * included in all copies or substantial portions of the Software.
> > + *
> > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
> > + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
> > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
> > + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
> > + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
> > + * OTHER DEALINGS IN THE SOFTWARE.
> > + */
> > +
> > +#include "sun50i-a64-pine64.dts"
> > +
> > +/ {
> > + model = "Pine64+";
> > + compatible = "pine64,pine64-plus", "allwinner,sun50i-a64";
> > +
> > + /* TODO: Camera, Ethernet PHY, touchscreen, etc. */
> > +};
> > diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
> > new file mode 100644
> > index 000000000000..9f127b3d0e33
> > --- /dev/null
> > +++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64.dts
> > @@ -0,0 +1,74 @@
> > +/*
> > + * Copyright (c) 2016 ARM Ltd.
> > + *
> > + * This file is dual-licensed: you can use it either under the terms
> > + * of the GPL or the X11 license, at your option. Note that this dual
> > + * licensing only applies to this file, and not this project as a
> > + * whole.
> > + *
> > + * a) This library is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License as
> > + * published by the Free Software Foundation; either version 2 of the
> > + * License, or (at your option) any later version.
> > + *
> > + * This library is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> > + * GNU General Public License for more details.
> > + *
> > + * Or, alternatively,
> > + *
> > + * b) Permission is hereby granted, free of charge, to any person
> > + * obtaining a copy of this software and associated documentation
> > + * files (the "Software"), to deal in the Software without
> > + * restriction, including without limitation the rights to use,
> > + * copy, modify, merge, publish, distribute, sublicense, and/or
> > + * sell copies of the Software, and to permit persons to whom the
> > + * Software is furnished to do so, subject to the following
> > + * conditions:
> > + *
> > + * The above copyright notice and this permission notice shall be
> > + * included in all copies or substantial portions of the Software.
> > + *
> > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
> > + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
> > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
> > + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
> > + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
> > + * OTHER DEALINGS IN THE SOFTWARE.
> > + */
> > +
> > +/dts-v1/;
> > +
> > +#include "sun50i-a64.dtsi"
> > +
> > +/ {
> > + model = "Pine64";
> > + compatible = "pine64,pine64", "allwinner,sun50i-a64";
> > +
> > + aliases {
> > + serial0 = &uart0;
> > + };
> > +
> > + chosen {
> > + stdout-path = "serial0:115200n8";
> > + };
> > +};
> > +
> > +&uart0 {
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&uart0_pins_a>;
> > + status = "okay";
> > +};
> > +
> > +&i2c1 {
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&i2c1_pins>;
> > + status = "okay";
> > +};
> > +
> > +&i2c1_pins {
> > + allwinner,pull = <SUN4I_PINCTRL_PULL_UP>;
>
> which should translate to:
> bias-pull-up;
> in the generic binding, right?
Fixed and applied, thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161103/b004eabe/attachment.sig>
^ permalink raw reply
* [PATCH 09/13] mmc: dw_mmc: remove the "clock-freq-min-max" property
From: Heiko Stübner @ 2016-11-03 8:20 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103062135.10697-10-jh80.chung@samsung.com>
Hi Jaehoon,
Am Donnerstag, 3. November 2016, 15:21:31 schrieb Jaehoon Chung:
> Remove the "clock-freq-min-max" property.
> There is "max-frequency" property in drivers/mmc/core/host.c
> It can be used for getting maximum frequency.
>
> And minimum clock value is assigned to 100K by default.
> Because MMC core should check the initial clock value from
> 400K to 100K until finding correct value.
> It's why Minimum value puts 100K by default.
>
> Signed-off-by: Jaehoon Chung <jh80.chung@samsung.com>
> ---
> Documentation/devicetree/bindings/mmc/synopsys-dw-mshc.txt | 3 ---
> drivers/mmc/host/dw_mmc.c | 13
> ++++--------- 2 files changed, 4 insertions(+), 12 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mmc/synopsys-dw-mshc.txt
> b/Documentation/devicetree/bindings/mmc/synopsys-dw-mshc.txt index
> 4e00e85..21c8251 100644
> --- a/Documentation/devicetree/bindings/mmc/synopsys-dw-mshc.txt
> +++ b/Documentation/devicetree/bindings/mmc/synopsys-dw-mshc.txt
> @@ -56,9 +56,6 @@ Optional properties:
> is specified and the ciu clock is specified then we'll try to set the ciu
> clock to this at probe time.
>
> -* clock-freq-min-max: Minimum and Maximum clock frequency for card output
> - clock(cclk_out). If it's not specified, max is 200MHZ and min is 400KHz
> by default. -
I'd think devicetree people will protest about simply removing this property
without deprecating it first - to be backwards compatible with old devicetrees.
Especially as this property was added already back in 2013, so has been in use
for quite some time.
Heiko
^ permalink raw reply
* [PATCH 02/10] iio: adc: Add stm32 support
From: Fabrice Gasnier @ 2016-11-03 8:20 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <a1445535-5f86-e95d-ed38-03edb8c9f9d8@kernel.org>
On 10/30/2016 04:27 PM, Jonathan Cameron wrote:
> On 25/10/16 17:25, Fabrice Gasnier wrote:
>> This patch adds support for STMicroelectronics STM32 MCU's analog to
>> digital converter.
>>
>> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
> Hi Fabrice,
>
> Sometimes I hate SoC ADCs. For some reason the hardware designers seem to
> try and throw everything and the kitchen sink at them. Discontinuous mode
> as an example in this device. Not seen that particular piece of fun before
> and glad to see you haven't 'yet' tried to support it!
>
> Anyhow, the complexity of the hardware leads to an initially complex driver.
> My first thought it that this would be easier to follow / review if we
> built it up in smaller steps. Perhaps ditch the injected channel support
> entirely in the first instance. I also wonder if you don't need to support
> that whole thing (injected sampling) as another iio device entirely using the
> same channels. That's kind of what it is from a data flow point of view
> (we've had arbitary sequencers before with priorities - don't think anyone
> ever decided the pain was worth supporting the complexity, but right answer
> has always been multiple IIO devices).
Hi Jonathan,
First, many thanks for your review. I agree with you, most reasonable
approach is to remove some
complexity to ease the review. Regarding injected support, basically,
bellow approach is to use
separate IIO devices for regular and injected. But, I'll remove this, at
least for now, in next patch set.
> You also have at least one layer of abstraction in here that serves no
> current purpose. Please clear that out for now. It'll make the code
> shorter and easier to follow. If/when other parts are introduced then
> is the time to do that transistion to having the abstraction.
From your suggestion, this may end-up in a single driver file in
drivers/iio.
I think I'll try to keep simple routines like start, stop, conf_scan and
so on, but
remove indirection routines from stm32-adc.h file (e.g. stm32_adc_ops).
Is it in line with your suggestions ?
>
> My first thought on the double / tripple adc handling is that you'd be better
> off handling them as 3 separate devices then doing some 'unusual' trigger
> handling to support the weird sequencing. Guessing you thought about that?
> If so could you lay out your reasoning for the single driver instance approach.
> I'm not arguing against it btw, merely want to understand your reasoning!
I mainly came up with a single driver instance approach because there
are basically
3 identical ADC instances 'mapped' in a single IP with few common resources.
I usually see mfd are more heterogeneous and declare cells for various
subsystem drivers.
But I can try to move to mfd as you're suggesting.
I just hope this will not bring more complexity.
>
> It would be tricky given one set of channels are selectable over 3 devices
> and there are constraints to enforce (not sampling same channel on two ADCs
> at the same time) but not impossible... Perhaps what you have here is
> indeed simpler!
>
> Whilst it's been a nasty job to review, I'm guessing writing it was
> much worse ;) Pretty good starting point though might take a little while
> to pin down the remaining questions on how best to handle this particular
> monster.
My apologies... I hope you didn't had much of a headache :-) by reading me.
More questions bellow.
> Jonathan
>> ---
>> drivers/iio/adc/Kconfig | 2 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/stm32/Kconfig | 34 ++
>> drivers/iio/adc/stm32/Makefile | 4 +
>> drivers/iio/adc/stm32/stm32-adc.c | 999 ++++++++++++++++++++++++++++++++++++
>> drivers/iio/adc/stm32/stm32-adc.h | 442 ++++++++++++++++
>> drivers/iio/adc/stm32/stm32f4-adc.c | 574 +++++++++++++++++++++
>> 7 files changed, 2056 insertions(+)
>> create mode 100644 drivers/iio/adc/stm32/Kconfig
>> create mode 100644 drivers/iio/adc/stm32/Makefile
>> create mode 100644 drivers/iio/adc/stm32/stm32-adc.c
>> create mode 100644 drivers/iio/adc/stm32/stm32-adc.h
>> create mode 100644 drivers/iio/adc/stm32/stm32f4-adc.c
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index 7edcf32..5c96a55 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -583,4 +583,6 @@ config XILINX_XADC
>> The driver can also be build as a module. If so, the module will be called
>> xilinx-xadc.
>>
>> +source "drivers/iio/adc/stm32/Kconfig"
>> +
>> endmenu
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index 7a40c04..a9dbf3a 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -13,6 +13,7 @@ obj-$(CONFIG_AD7791) += ad7791.o
>> obj-$(CONFIG_AD7793) += ad7793.o
>> obj-$(CONFIG_AD7887) += ad7887.o
>> obj-$(CONFIG_AD799X) += ad799x.o
>> +obj-$(CONFIG_ARCH_STM32) += stm32/
>> obj-$(CONFIG_AT91_ADC) += at91_adc.o
>> obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o
>> obj-$(CONFIG_AXP288_ADC) += axp288_adc.o
>> diff --git a/drivers/iio/adc/stm32/Kconfig b/drivers/iio/adc/stm32/Kconfig
>> new file mode 100644
>> index 0000000..245d037
>> --- /dev/null
>> +++ b/drivers/iio/adc/stm32/Kconfig
>> @@ -0,0 +1,34 @@
>> +#
>> +# STM32 familly ADC drivers
>> +#
>> +
>> +config STM32_ADC
>> + tristate
>> + select REGULATOR
>> + select REGULATOR_FIXED_VOLTAGE
>> + select IIO_BUFFER
>> + select IIO_TRIGGERED_BUFFER
>> + help
>> + Say yes here to build the driver for the STMicroelectronics
>> + STM32 analog-to-digital converter (ADC).
>> +
>> + This driver can also be built as a module. If so, the module
>> + will be called stm32-adc.
>> +
>> +config STM32F4_ADC
>> + tristate "STMicroelectronics STM32F4 adc"
>> + depends on ARCH_STM32 || COMPILE_TEST
>> + depends on OF
>> + select STM32_ADC
>> + help
>> + Say yes here to build support for STMicroelectronics stm32f4 Analog
>> + to Digital Converter (ADC).
>> +
>> + This driver can also be built as a module. If so, the module
>> + will be called stm32f4-adc.
>> +
>> +config STM32_ADC_DEBUG
>> + bool "Enable debug for stm32 ADC drivers"
>> + depends on STM32_ADC
>> + help
>> + Say "yes" to enable debug messages, on stm32 ADC drivers.
>> diff --git a/drivers/iio/adc/stm32/Makefile b/drivers/iio/adc/stm32/Makefile
>> new file mode 100644
>> index 0000000..83e8154
>> --- /dev/null
>> +++ b/drivers/iio/adc/stm32/Makefile
>> @@ -0,0 +1,4 @@
>> +# Core
>> +subdir-ccflags-$(CONFIG_STM32_ADC_DEBUG) := -DDEBUG
>> +obj-$(CONFIG_STM32_ADC) += stm32-adc.o
>> +obj-$(CONFIG_STM32F4_ADC) += stm32f4-adc.o
>> diff --git a/drivers/iio/adc/stm32/stm32-adc.c b/drivers/iio/adc/stm32/stm32-adc.c
>> new file mode 100644
>> index 0000000..1e0850d
>> --- /dev/null
>> +++ b/drivers/iio/adc/stm32/stm32-adc.c
>> @@ -0,0 +1,999 @@
[snip]
>> +
>> +static int stm32_adc_conf_scan(struct iio_dev *indio_dev,
>> + const unsigned long *scan_mask)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> + int ret;
>> +
>> + ret = stm32_adc_clk_sel(adc);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "Clock sel failed\n");
>> + return ret;
>> + }
>> +
>> + ret = stm32_adc_enable(adc);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "Failed to enable adc\n");
>> + return ret;
>> + }
>> +
>> + ret = stm32_adc_conf_scan_seq(indio_dev, scan_mask);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "Failed to configure sequence\n");
>> + goto err_dis;
>> + }
> It's horrible but to end up in the 'obvious' state I'd disable the adc
> again assuming that doesn't kill the stuff that is configured.
I'll check this and try to come up with something.
>> +
>> + return 0;
>> +
>> +err_dis:
>> + stm32_adc_disable(adc);
>> +
>> + return ret;
>> +}
>> +
[snip]
>> +/**
>> + * stm32_adc_single_conv() - perform a single conversion
>> + * @indio_dev: IIO device
>> + * @chan: IIO channel
>> + * @result: conversion result
>> + *
>> + * The function performs a single conversion on a given channel, by
>> + * by:
>> + * - creating scan mask with only one channel
>> + * - using SW trigger
>> + * - then start single conv
>> + */
>> +static int stm32_adc_single_conv(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + int *val)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> + unsigned long *scan_mask;
>> + long timeout;
>> + u16 result;
>> + int ret;
>> +
>> + scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength), sizeof(long),
>> + GFP_KERNEL);
> This is known maximum length... I'd just avoid the complexity of allocating
> it like this - a comment would do the job to say it is the right length.
Do you suggest to use a predefined variable (like unsigned long
scan_mask) directly ?
And add a more basic test on 'masklength', to be sure ?
>> + if (!scan_mask)
>> + return -ENOMEM;
>> +
>> + set_bit(chan->scan_index, scan_mask);
>> +
>> + reinit_completion(&adc->completion);
>> +
>> + adc->bufi = 0;
>> + adc->num_conv = 1;
>> + adc->buffer = &result;
>> +
>> + ret = stm32_adc_conf_scan(indio_dev, scan_mask);
>> + if (ret)
>> + goto free;
>> +
>> + /* No HW trigger: conversion can be launched in SW */
>> + ret = stm32_adc_set_trig(indio_dev, NULL);
> Put it back again afterwards? Otherwise some nasty race conditions look
> likely to me.. (userspace sets trigger and is about to enable the buffer
> when along comes this code and changes it underneath).
I'll fix this.
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "Can't set SW trigger\n");
>> + goto adc_disable;
>> + }
>> +
>> + stm32_adc_conv_irq_enable(adc);
>> +
>> + ret = stm32_adc_start_conv(adc);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "Failed to start single conv\n");
>> + goto irq_disable;
>> + }
>> +
>> + timeout = wait_for_completion_interruptible_timeout(
>> + &adc->completion, STM32_ADC_TIMEOUT);
>> + if (timeout == 0) {
>> + dev_warn(&indio_dev->dev, "Conversion timed out!\n");
>> + ret = -ETIMEDOUT;
>> + } else if (timeout < 0) {
>> + dev_warn(&indio_dev->dev, "Interrupted conversion!\n");
>> + ret = -EINTR;
>> + } else {
>> + *val = result & STM32_RESULT_MASK;
>> + ret = IIO_VAL_INT;
>> + }
>> +
>> + if (stm32_adc_stop_conv(adc))
>> + dev_err(&indio_dev->dev, "stop failed\n");
>> +
>> +irq_disable:
>> + stm32_adc_conv_irq_disable(adc);
>> +
>> +adc_disable:
>> + stm32_adc_disable(adc);
>> +
>> +free:
>> + kfree(scan_mask);
>> + adc->buffer = NULL;
>> +
>> + return ret;
>> +}
>> +
>> +static int stm32_adc_read_raw(struct iio_dev *indio_dev,
>> + struct iio_chan_spec const *chan,
>> + int *val, int *val2, long mask)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> + int ret = -EINVAL;
>> +
>> + switch (mask) {
>> + case IIO_CHAN_INFO_RAW:
>> + ret = iio_device_claim_direct_mode(indio_dev);
>> + if (ret)
>> + return ret;
>> + if (chan->type == IIO_VOLTAGE)
>> + ret = stm32_adc_single_conv(indio_dev, chan, val);
>> + iio_device_release_direct_mode(indio_dev);
>> + break;
>> + case IIO_CHAN_INFO_SCALE:
>> + *val = adc->common->vref_mv;
>> + *val2 = chan->scan_type.realbits;
>> + ret = IIO_VAL_FRACTIONAL_LOG2;
>> + break;
>> + default:
>> + break;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +/**
>> + * stm32_adc_isr() - Treat interrupt for one ADC instance within ADC block
> As this is kernel doc please document the parameter as well. Otherwise
> we'll get a pile of warnings!
Sure.
>> + */
>> +static irqreturn_t stm32_adc_isr(struct stm32_adc *adc)
>> +{
>> + struct iio_dev *indio_dev = iio_priv_to_dev(adc);
>> + const struct stm32_adc_reginfo *reginfo =
>> + adc->common->data->adc_reginfo;
>> + u32 mask, clr_mask, status = stm32_adc_readl(adc, reginfo->isr);
>> +
>> + if (adc->injected) {
>> + mask = reginfo->jeoc;
>> + clr_mask = mask;
>> + } else {
>> + mask = reginfo->eoc;
>> + /* don't clear 'eoc' as it is cleared when reading 'dr' */
>> + clr_mask = 0;
>> + }
>> +
>> + /* clear irq */
>> + stm32_adc_writel(adc, reginfo->isr, status & ~clr_mask);
> Want to do this in the non injected case? it's a noop isn't it?
I'll rework this.
>
>> + status &= mask;
>> +
>> + /* Regular data */
>> + if (status & reginfo->eoc) {
> Hmm.. this is a little bit of 'missuse' of the standard trigger architecture
> but as long as it's restricted to just this device I don't suppose we need
> to care. Only reason we need it is to provide control of 'which' hardware
> trigger is being used.
>
> Guessing the DMA will almost always be turned on and will make this oddity
> effectively disappear.
I'm not sure to understand your remark. Above test checks end of
conversion status flag.
Or do you talk about bellow lines ? Can you please clarify ?
>> + adc->buffer[adc->bufi] = stm32_adc_readl(adc, reginfo->dr);
>> + if (iio_buffer_enabled(indio_dev)) {
>> + adc->bufi++;
>> + if (adc->bufi >= adc->num_conv) {
>> + stm32_adc_conv_irq_disable(adc);
>> + iio_trigger_poll(indio_dev->trig);
>> + }
>> + } else {
>> + complete(&adc->completion);
>> + }
>> + }
>> +
>> + /* Injected data */
>> + if (status & reginfo->jeoc) {
>> + int i;
>> +
>> + for (i = 0; i < adc->num_conv; i++) {
>> + adc->buffer[i] = stm32_adc_readl(adc, reginfo->jdr[i]);
>> + adc->bufi++;
>> + }
>> +
>> + if (iio_buffer_enabled(indio_dev)) {
>> + stm32_adc_conv_irq_disable(adc);
>> + iio_trigger_poll(indio_dev->trig);
>> + } else {
>> + complete(&adc->completion);
>> + }
>> + }
>> +
>> + /*
>> + * In case end of conversion flags have been handled, this has been
>> + * handled for this ADC instance
>> + */
>> + if (status)
>> + return IRQ_HANDLED;
>> +
>> + /* This adc instance didn't trigger this interrupt */
>> + return IRQ_NONE;
>> +}
>> +
>> +/**
>> + * stm32_adc_common_isr() - Common isr for the whole ADC block
>> + *
>> + * There is one IRQ for all ADCs in ADC block, check all instances.
>> + */
>> +static irqreturn_t stm32_adc_common_isr(int irq, void *data)
>> +{
>> + struct stm32_adc_common *common = data;
>> + irqreturn_t ret = IRQ_NONE;
>> + struct stm32_adc *adc;
>> +
>> + list_for_each_entry(adc, &common->adc_list, adc_list)
>> + ret |= stm32_adc_isr(adc);
> Hmm.. ret |= is rather fragile. Preferable to make the handling of NONE
> vs IRQ_HANDLED explicit.
>
> If you were to split the driver up as I suggested might make sense above,
> then this would be done with an irq chip in a top level device (effectively
> a very simple mfd).
I'll look into it.
>> +
>> + return ret;
>> +}
>> +
>> +/**
>> + * stm32_adc_validate_trigger() - validate trigger for stm32 adc
>> + * @indio_dev: IIO device
>> + * @trig: new trigger
>> + *
>> + * Returns: 0 if trig matches one of the triggers registered by stm32 adc
>> + * driver, -EINVAL otherwise.
>> + */
>> +static int stm32_adc_validate_trigger(struct iio_dev *indio_dev,
>> + struct iio_trigger *trig)
>> +{
>> + return stm32_adc_get_trig_index(indio_dev, trig) < 0 ? -EINVAL : 0;
>> +}
>> +
>> +static int stm32_adc_update_scan_mode(struct iio_dev *indio_dev,
>> + const unsigned long *scan_mask)
> I'm glad you kept this relatively simple compared to some of the
> 'fun' the hardware is capable of. Very wise!
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> + int ret;
>> + u32 bit;
>> +
>> + adc->num_conv = 0;
>> + for_each_set_bit(bit, scan_mask, indio_dev->masklength)
>> + adc->num_conv++;
>> +
>> + ret = stm32_adc_conf_scan(indio_dev, scan_mask);
>> + if (ret)
>> + return ret;
>> +
>> + return 0;
>> +}
>> +
>> +static int stm32_adc_of_xlate(struct iio_dev *indio_dev,
>> + const struct of_phandle_args *iiospec)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < indio_dev->num_channels; i++)
>> + if (indio_dev->channels[i].channel == iiospec->args[0])
>> + return i;
>> +
>> + return -EINVAL;
>> +}
>> +
>> +/**
>> + * stm32_adc_debugfs_reg_access - read or write register value
>> + *
>> + * To read a value from an ADC register:
>> + * echo [ADC reg offset] > direct_reg_access
>> + * cat direct_reg_access
>> + *
>> + * To write a value in a ADC register:
>> + * echo [ADC_reg_offset] [value] > direct_reg_access
>> + */
>> +static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev,
>> + unsigned reg, unsigned writeval,
>> + unsigned *readval)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> +
>> + if (!readval)
>> + stm32_adc_writel(adc, reg, writeval);
>> + else
>> + *readval = stm32_adc_readl(adc, reg);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct iio_info stm32_adc_iio_info = {
>> + .read_raw = stm32_adc_read_raw,
>> + .validate_trigger = stm32_adc_validate_trigger,
>> + .update_scan_mode = stm32_adc_update_scan_mode,
>> + .debugfs_reg_access = stm32_adc_debugfs_reg_access,
>> + .of_xlate = stm32_adc_of_xlate,
>> + .driver_module = THIS_MODULE,
>> +};
>> +
>> +static int stm32_adc_buffer_postdisable(struct iio_dev *indio_dev)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> +
>> + stm32_adc_disable(adc);
> This is a surprise as postdisbale should balance preenable...
> Ah, you have update scan mode enabling the adc. If you can balance it
> better by moving that to preenable please do as it is more 'obviously' correct.
I'll try to rework this.
>> +
>> + return 0;
>> +}
>> +
>> +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = {
>> + .postenable = &iio_triggered_buffer_postenable,
>> + .predisable = &iio_triggered_buffer_predisable,
>> + .postdisable = &stm32_adc_buffer_postdisable,
>> +};
>> +
>> +static int stm32_adc_validate_device(struct iio_trigger *trig,
>> + struct iio_dev *indio_dev)
>> +{
>> + struct iio_dev *indio = iio_trigger_get_drvdata(trig);
>> +
>> + return indio != indio_dev ? -EINVAL : 0;
>> +}
>> +
>> +static int stm32_adc_set_trigger_state(struct iio_trigger *trig,
>> + bool state)
>> +{
>> + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> + int ret;
>> +
>> + if (state) {
>> + /* Reset adc buffer index */
>> + adc->bufi = 0;
>> +
>> + /* Allocate adc buffer */
>> + adc->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL);
> I'd be more cynical. It's not that big a memory allocation at worst.
> Just put a big enough buffer in your adc structure and don't bother doing
> it dynamically.
>
> If you didn't want to do it, it should be in the preenable callback rather
> than the trigger state one (for semantic reasons rather than because it's a
> bug)
I'll fix this.
>> + if (!adc->buffer)
>> + return -ENOMEM;
>> +
>> + ret = stm32_adc_set_trig(indio_dev, trig);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "Can't set trigger\n");
>> + goto err_buffer_free;
>> + }
>> +
>> + stm32_adc_conv_irq_enable(adc);
>> +
>> + ret = stm32_adc_start_conv(adc);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "Failed to start\n");
>> + goto err_irq_trig_disable;
>> + }
>> + } else {
>> + ret = stm32_adc_stop_conv(adc);
>> + if (ret < 0) {
>> + dev_err(&indio_dev->dev, "Failed to stop\n");
>> + return ret;
>> + }
>> +
>> + stm32_adc_conv_irq_disable(adc);
>> +
>> + ret = stm32_adc_set_trig(indio_dev, NULL);
>> + if (ret)
>> + dev_warn(&indio_dev->dev, "Can't clear trigger\n");
>> +
>> + kfree(adc->buffer);
>> + adc->buffer = NULL;
>> + }
>> +
>> + return 0;
>> +
>> +err_irq_trig_disable:
>> + stm32_adc_conv_irq_disable(adc);
>> + stm32_adc_set_trig(indio_dev, NULL);
>> +
>> +err_buffer_free:
>> + kfree(adc->buffer);
>> + adc->buffer = NULL;
>> +
>> + return ret;
>> +}
>> +
>> +static const struct iio_trigger_ops stm32_adc_trigger_ops = {
>> + .owner = THIS_MODULE,
>> + .validate_device = stm32_adc_validate_device,
>> + .set_trigger_state = stm32_adc_set_trigger_state,
>> +};
>> +
>> +static irqreturn_t stm32_adc_trigger_handler(int irq, void *p)
>> +{
>> + struct iio_poll_func *pf = p;
>> + struct iio_dev *indio_dev = pf->indio_dev;
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> +
>> + dev_dbg(&indio_dev->dev, "%s bufi=%d\n", __func__, adc->bufi);
>> +
>> + /* reset buffer index */
>> + adc->bufi = 0;
>> + iio_push_to_buffers_with_timestamp(indio_dev, adc->buffer,
>> + pf->timestamp);
>> +
>> + iio_trigger_notify_done(indio_dev->trig);
>> +
>> + /* re-enable eoc irq */
>> + stm32_adc_conv_irq_enable(adc);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void stm32_adc_trig_unregister(struct iio_dev *indio_dev)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> + struct iio_trigger *trig, *_t;
>> +
>> + list_for_each_entry_safe(trig, _t, &adc->extrig_list, alloc_list) {
>> + iio_trigger_unregister(trig);
>> + list_del(&trig->alloc_list);
>> + }
>> +}
>> +
> I'd like a bit of documentation on this and a few of the other more
> complex functions. Here it wasn't immediately obvious to me that it
> was registering a large set of triggers. Also, silly question but
> do you have any means of controlling the various timer setups from userspace?
Sorry about this, I'll try to comment about trigger list to make it more
obvious.
There is no mean to setup timers via userspace, yet...
I can remove them from the list for now, until this is supported.
BTW I have some questions on trigger...
>
> There have been numerous discussions over the years on having a generic
> timer subsystem, but if anything got written it passed me by. I have a couple
> of boards where it would be handy but never had the time to do more than
> talk about it ;)
This is interesting... I'd be glad to hear more about it. Can you point
some discussions if you have it in mind?
In this driver, validate_trigger routine enforces that only triggers
allocated for current indio_dev can be used.
What if all timer triggers are put in a separate driver ? (e.g. like
hrtimer in drivers/iio/trigger/) ?
Purpose would be to tune 'sampling_frequency' and so on, on similar
model, and have it configured basically when using it
(e.g. cat trigger/name>trigger/current_trigger.).
Is it a viable option, not to declare timer triggers in stm32-adc.c, but
use pre-defined list of triggers, and separate trigger driver ?
I'm thinking then, of simple string based list... But maybe you already
though about this king of things ?
Please kindly share you view on this.
Thanks again for your review.
Best Regards,
Fabrice
>> +static int stm32_adc_trig_register(struct iio_dev *indio_dev)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> + struct stm32_adc_common *common = adc->common;
>> + const struct stm32_adc_trig_info *ext = common->data->ext_triggers;
>> + struct iio_trigger *trig;
>> + int i, ret = 0;
>> +
>> + if (adc->injected)
>> + ext = common->data->jext_triggers;
>> + else
>> + ext = common->data->ext_triggers;
>> +
>> + for (i = 0; ext && ext[i].name; i++) {
>> + trig = devm_iio_trigger_alloc(common->dev, "%s_%s%d_%s",
>> + indio_dev->name,
>> + adc->injected ? "jext" : "ext",
>> + ext[i].extsel, ext[i].name);
>> + if (!trig) {
>> + dev_err(common->dev, "trig %s_%s%d_%s alloc failed\n",
>> + indio_dev->name,
>> + adc->injected ? "jext" : "ext",
>> + ext[i].extsel, ext[i].name);
>> + ret = -ENOMEM;
>> + goto err;
>> + }
>> +
>> + trig->dev.parent = common->dev;
>> + trig->ops = &stm32_adc_trigger_ops;
>> + iio_trigger_set_drvdata(trig, indio_dev);
>> +
>> + ret = iio_trigger_register(trig);
>> + if (ret) {
>> + dev_err(common->dev,
>> + "trig %s_%s%d_%s register failed\n",
>> + indio_dev->name,
>> + adc->injected ? "jext" : "ext",
>> + ext[i].extsel, ext[i].name);
>> + goto err;
>> + }
>> +
>> + list_add_tail(&trig->alloc_list, &adc->extrig_list);
>> + }
>> +
>> + return 0;
>> +err:
>> + stm32_adc_trig_unregister(indio_dev);
>> +
>> + return ret;
>> +}
>> +
>> +static void stm32_adc_chan_init_one(struct iio_dev *indio_dev,
>> + struct iio_chan_spec *chan,
>> + const struct stm32_adc_chan_spec *channel,
>> + int scan_index)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> +
>> + chan->type = channel->type;
>> + chan->channel = channel->channel;
>> + chan->datasheet_name = channel->name;
>> + chan->extend_name = channel->name;
>> + chan->scan_index = scan_index;
>> + chan->indexed = 1;
>> + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
>> + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
>> + chan->scan_type.sign = 'u';
>> + chan->scan_type.realbits = adc->common->data->highres;
>> + chan->scan_type.storagebits = STM32_STORAGEBITS;
> This is one of those cases where actually I'd argue just having the number
> here and not under a define would be clearer! So just put 16 here.
>> + chan->scan_type.shift = 0;
> Should be unneeded. Shift of 0 is the obvious default so no info provided
> to readers of the code either really.
I'll fix this
>> +}
>> +
>> +static int stm32_adc_chan_of_init(struct iio_dev *indio_dev,
>> + const struct stm32_adc_info *adc_info)
>> +{
>> + struct stm32_adc *adc = iio_priv(indio_dev);
>> + struct device_node *node = indio_dev->dev.of_node;
>> + struct property *prop;
>> + const __be32 *cur;
>> + struct iio_chan_spec *channels;
>> + int scan_index = 0, num_channels = 0;
>> + u32 val;
>> +
>> + of_property_for_each_u32(node, "st,adc-channels", prop, cur, val)
>> + num_channels++;
>> +
>> + channels = devm_kcalloc(&indio_dev->dev, num_channels,
>> + sizeof(struct iio_chan_spec), GFP_KERNEL);
>> + if (!channels)
>> + return -ENOMEM;
>> +
>> + of_property_for_each_u32(node, "st,adc-channels", prop, cur, val) {
>> + stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
>> + &adc_info->channels[val],
>> + scan_index);
>> + scan_index++;
>> + }
>> +
>> + adc->max_channels = adc_info->max_channels;
>> + indio_dev->num_channels = scan_index;
>> + indio_dev->channels = channels;
>> +
>> + return 0;
>> +}
>> +
>> +static int stm32_adc_register(struct stm32_adc_common *common,
>> + struct device_node *child)
>> +{
>> + struct iio_dev *indio_dev;
>> + struct stm32_adc *adc;
>> + int i, ret;
>> + u32 reg;
>> +
>> + ret = of_property_read_u32(child, "reg", ®);
>> + if (ret != 0) {
>> + dev_err(common->dev, "missing reg property\n");
>> + return -EINVAL;
>> + }
>> +
>> + for (i = 0; common->data->adc_info[i].channels; i++)
>> + if (common->data->adc_info[i].reg == reg)
>> + break;
>> +
>> + if (i >= STM32_ADC_ID_MAX || !common->data->adc_info[i].channels) {
>> + dev_err(common->dev, "bad adc reg offset\n");
>> + return -ENOENT;
>> + }
>> +
>> + indio_dev = devm_iio_device_alloc(common->dev, sizeof(*adc));
>> + if (!indio_dev) {
>> + dev_err(common->dev, "iio device allocation failed\n");
>> + return -ENOMEM;
>> + }
>> +
>> + adc = iio_priv(indio_dev);
>> + adc->id = i;
>> + adc->offset = reg;
>> + adc->common = common;
>> + INIT_LIST_HEAD(&adc->extrig_list);
>> + spin_lock_init(&adc->lock);
>> + init_completion(&adc->completion);
>> +
>> + if (child->name)
>> + indio_dev->name = child->name;
>> + else
>> + indio_dev->name = common->data->adc_info[i].name;
>> + indio_dev->dev.parent = common->dev;
>> + indio_dev->dev.of_node = child;
>> + indio_dev->info = &stm32_adc_iio_info;
>> + indio_dev->modes = INDIO_DIRECT_MODE;
>> +
>> + if (of_property_read_bool(child, "st,injected")) {
>> + dev_dbg(common->dev, "%s Configured to use injected\n",
>> + indio_dev->name);
>> + adc->injected = true;
>> + }
>> +
>> + adc->clk = of_clk_get(child, 0);
>> + if (IS_ERR(adc->clk)) {
>> + adc->clk = NULL;
>> + dev_dbg(common->dev, "No child clk found\n");
>> + } else {
>> + ret = clk_prepare_enable(adc->clk);
>> + if (ret < 0)
>> + goto err_clk_put;
>> + }
>> +
>> + ret = stm32_adc_chan_of_init(indio_dev, &common->data->adc_info[i]);
>> + if (ret < 0) {
>> + dev_err(common->dev, "iio channels init failed\n");
>> + goto err_clk_disable;
>> + }
>> +
>> + ret = stm32_adc_trig_register(indio_dev);
>> + if (ret)
>> + goto err_clk_disable;
>> +
>> + ret = iio_triggered_buffer_setup(indio_dev,
>> + &iio_pollfunc_store_time,
>> + &stm32_adc_trigger_handler,
>> + &iio_triggered_buffer_setup_ops);
>> + if (ret) {
>> + dev_err(common->dev, "buffer setup failed\n");
>> + goto err_trig_unregister;
>> + }
>> +
>> + ret = iio_device_register(indio_dev);
>> + if (ret) {
>> + dev_err(common->dev, "iio dev register failed\n");
>> + goto err_buffer_cleanup;
>> + }
>> +
>> + list_add_tail(&adc->adc_list, &common->adc_list);
>> +
>> + return 0;
>> +
>> +err_buffer_cleanup:
>> + iio_triggered_buffer_cleanup(indio_dev);
>> +
>> +err_trig_unregister:
>> + stm32_adc_trig_unregister(indio_dev);
>> +
>> +err_clk_disable:
>> + if (adc->clk)
>> + clk_disable_unprepare(adc->clk);
>> +
>> +err_clk_put:
>> + if (adc->clk)
>> + clk_put(adc->clk);
>> +
>> + return ret;
>> +}
>> +
>> +static void stm32_adc_unregister(struct stm32_adc *adc)
>> +{
>> + struct iio_dev *indio_dev = iio_priv_to_dev(adc);
>> +
>> + iio_device_unregister(indio_dev);
>> + iio_triggered_buffer_cleanup(indio_dev);
>> + stm32_adc_trig_unregister(indio_dev);
>> + if (adc->clk) {
>> + clk_disable_unprepare(adc->clk);
>> + clk_put(adc->clk);
>> + }
>> +}
>> +
>> +int stm32_adc_probe(struct platform_device *pdev)
>> +{
>> + struct device_node *np = pdev->dev.of_node, *child;
>> + struct device *dev = &pdev->dev;
>> + const struct of_device_id *match;
>> + struct stm32_adc_common *common;
>> + struct stm32_adc *adc;
>> + struct resource *res;
>> + int ret;
>> +
>> + match = of_match_device(dev->driver->of_match_table, &pdev->dev);
>> + if (!match || !match->data) {
>> + dev_err(&pdev->dev, "compatible data not provided\n");
> How would we have instantiated this if there was not a suitable match?
> As such what does this check give us? (confused!)
I'll fix this.
>> + return -EINVAL;
>> + }
>> +
>> + common = devm_kzalloc(&pdev->dev, sizeof(*common), GFP_KERNEL);
>> + if (!common)
>> + return -ENOMEM;
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + common->base = devm_ioremap_resource(&pdev->dev, res);
>> + if (IS_ERR(common->base))
>> + return PTR_ERR(common->base);
>> +
>> + common->data = match->data;
>> + common->dev = &pdev->dev;
>> + platform_set_drvdata(pdev, common);
>> + mutex_init(&common->lock);
>> + INIT_LIST_HEAD(&common->adc_list);
>> +
>> + common->vref = devm_regulator_get(&pdev->dev, "vref");
>> + if (IS_ERR(common->vref)) {
>> + ret = PTR_ERR(common->vref);
>> + dev_err(&pdev->dev, "vref get failed, %d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = regulator_enable(common->vref);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "vref enable failed\n");
>> + return ret;
>> + }
>> +
>> + ret = regulator_get_voltage(common->vref);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "vref get voltage failed, %d\n", ret);
>> + goto err_regulator_disable;
>> + }
>> + common->vref_mv = ret / 1000;
>> + dev_dbg(&pdev->dev, "vref+=%dmV\n", common->vref_mv);
>> +
>> + common->aclk = devm_clk_get(&pdev->dev, "adc");
>> + if (IS_ERR(common->aclk)) {
>> + ret = PTR_ERR(common->aclk);
>> + dev_err(&pdev->dev, "Can't get 'adc' clock\n");
>> + goto err_regulator_disable;
>> + }
>> +
>> + ret = clk_prepare_enable(common->aclk);
>> + if (ret < 0) {
>> + dev_err(common->dev, "adc clk enable failed\n");
>> + goto err_regulator_disable;
>> + }
>> +
>> + common->irq = platform_get_irq(pdev, 0);
>> + if (common->irq < 0) {
>> + dev_err(&pdev->dev, "failed to get irq\n");
>> + ret = common->irq;
>> + goto err_clk_disable;
>> + }
>> +
>> + ret = devm_request_irq(&pdev->dev, common->irq, stm32_adc_common_isr,
>> + 0, pdev->name, common);
>> + if (ret) {
>> + dev_err(&pdev->dev, "failed to request irq\n");
>> + goto err_clk_disable;
>> + }
>> +
>> + /* Parse adc child nodes to retrieve master/slave instances data */
>> + for_each_available_child_of_node(np, child) {
>> + ret = stm32_adc_register(common, child);
>> + if (ret)
>> + goto err_unregister;
>> + }
>> +
>> + dev_info(&pdev->dev, "registered\n");
> No benefit in this info being provided (it's obvious, device just turned up
> in sysfs :) So drop it.
I'll fix this.
>> +
>> + return 0;
>> +
>> +err_unregister:
>> + list_for_each_entry(adc, &common->adc_list, adc_list)
>> + stm32_adc_unregister(adc);
>> +
>> +err_clk_disable:
>> + clk_disable_unprepare(common->aclk);
>> +
>> +err_regulator_disable:
>> + regulator_disable(common->vref);
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(stm32_adc_probe);
>> +
>> +int stm32_adc_remove(struct platform_device *pdev)
>> +{
>> + struct stm32_adc_common *common = platform_get_drvdata(pdev);
>> + struct stm32_adc *adc;
>> +
>> + list_for_each_entry(adc, &common->adc_list, adc_list)
>> + stm32_adc_unregister(adc);
>> + clk_disable_unprepare(common->aclk);
>> + regulator_disable(common->vref);
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(stm32_adc_remove);
>> +
>> +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
>> +MODULE_DESCRIPTION("STMicroelectronics STM32 ADC driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/iio/adc/stm32/stm32-adc.h b/drivers/iio/adc/stm32/stm32-adc.h
>> new file mode 100644
>> index 0000000..0be603c
>> --- /dev/null
>> +++ b/drivers/iio/adc/stm32/stm32-adc.h
>> @@ -0,0 +1,442 @@
>> +/*
>> + * This file is part of STM32 ADC driver
>> + *
>> + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
>> + * Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
>> + *
>> + * License type: GPLv2
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms of the GNU General Public License version 2 as published by
>> + * the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful, but
>> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
>> + * or FITNESS FOR A PARTICULAR PURPOSE.
>> + * See the GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License along with
>> + * this program. If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#ifndef __STM32_ADC_H
>> +#define __STM32_ADC_H
>> +
>> +/*
>> + * STM32 - ADC global register map
>> + * ________________________________________________________
>> + * | Offset | Register |
>> + * --------------------------------------------------------
>> + * | 0x000 | Master ADC1 |
>> + * --------------------------------------------------------
>> + * | 0x100 | Slave ADC2 |
>> + * --------------------------------------------------------
>> + * | 0x200 | Slave ADC3 |
>> + * --------------------------------------------------------
>> + * | 0x300 | Master & Slave common regs |
>> + * --------------------------------------------------------
>> + */
>> +#define STM32_ADCX_COMN_OFFSET 0x300
>> +#define STM32_ADC_ID_MAX 3
>> +#define STM32_ADC_MAX_SQ 16 /* SQ1..SQ16 */
>> +#define STM32_ADC_MAX_JSQ 4 /* JSQ1..JSQ4 */
>> +
>> +/* STM32 value masks */
>> +#define STM32_RESULT_MASK GENMASK(15, 0)
>> +#define STM32_STORAGEBITS 16
>> +
>> +/* External trigger enable for regular or injected channels (exten/jexten) */
>> +enum stm32_adc_exten {
>> + STM32_EXTEN_SWTRIG,
>> + STM32_EXTEN_HWTRIG_RISING_EDGE,
>> + STM32_EXTEN_HWTRIG_FALLING_EDGE,
>> + STM32_EXTEN_HWTRIG_BOTH_EDGES,
>> +};
>> +
>> +enum stm32_adc_extsel {
>> + STM32_EXT0,
>> + STM32_EXT1,
>> + STM32_EXT2,
>> + STM32_EXT3,
>> + STM32_EXT4,
>> + STM32_EXT5,
>> + STM32_EXT6,
>> + STM32_EXT7,
>> + STM32_EXT8,
>> + STM32_EXT9,
>> + STM32_EXT10,
>> + STM32_EXT11,
>> + STM32_EXT12,
>> + STM32_EXT13,
>> + STM32_EXT14,
>> + STM32_EXT15,
>> + STM32_EXT16,
>> + STM32_EXT17,
>> + STM32_EXT18,
>> + STM32_EXT19,
>> + STM32_EXT20,
>> + STM32_EXT21,
>> + STM32_EXT22,
>> + STM32_EXT23,
>> + STM32_EXT24,
>> + STM32_EXT25,
>> + STM32_EXT26,
>> + STM32_EXT27,
>> + STM32_EXT28,
>> + STM32_EXT29,
>> + STM32_EXT30,
>> + STM32_EXT31,
>> +};
>> +
>> +enum stm32_adc_jextsel {
>> + STM32_JEXT0,
>> + STM32_JEXT1,
>> + STM32_JEXT2,
>> + STM32_JEXT3,
>> + STM32_JEXT4,
>> + STM32_JEXT5,
>> + STM32_JEXT6,
>> + STM32_JEXT7,
>> + STM32_JEXT8,
>> + STM32_JEXT9,
>> + STM32_JEXT10,
>> + STM32_JEXT11,
>> + STM32_JEXT12,
>> + STM32_JEXT13,
>> + STM32_JEXT14,
>> + STM32_JEXT15,
>> + STM32_JEXT16,
>> + STM32_JEXT17,
>> + STM32_JEXT18,
>> + STM32_JEXT19,
>> + STM32_JEXT20,
>> + STM32_JEXT21,
>> + STM32_JEXT22,
>> + STM32_JEXT23,
>> + STM32_JEXT24,
>> + STM32_JEXT25,
>> + STM32_JEXT26,
>> + STM32_JEXT27,
>> + STM32_JEXT28,
>> + STM32_JEXT29,
>> + STM32_JEXT30,
>> + STM32_JEXT31,
>> +};
>> +
>> +#define STM32_ADC_TIMEOUT_US 100000
>> +#define STM32_ADC_TIMEOUT (msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000))
>> +
>> +/**
>> + * struct stm32_adc_chan_spec - specification of stm32 adc channel
>> + * @type: IIO channel type
>> + * @channel: channel number (single ended)
>> + * @name: channel name (single ended)
>> + */
>> +struct stm32_adc_chan_spec {
>> + enum iio_chan_type type;
>> + int channel;
>> + const char *name;
>> +};
>> +
>> +/**
>> + * struct stm32_adc_trig_info - ADC trigger info
>> + * @extsel: trigger selection for regular or injected
>> + * @name: name of the trigger, corresponding to its source
>> + */
>> +struct stm32_adc_trig_info {
>> + u32 extsel;
>> + const char *name;
>> +};
>> +
>> +/**
>> + * struct stm32_adc_info - stm32 ADC, per instance config data
>> + * @name: default name for this instance (like "adc1")
>> + * @reg: reg offset for this instance (e.g. 0x0 for adc1...)
>> + * @channels: Reference to stm32 channels spec
>> + * @max_channels: Number of single ended channels
>> + */
>> +struct stm32_adc_info {
>> + const char *name;
>> + u32 reg;
>> + const struct stm32_adc_chan_spec *channels;
>> + int max_channels;
>> +};
>> +
>> +/**
>> + * stm32_adc_regs - stm32 ADC misc registers & bitfield desc
>> + * @reg: register offset
>> + * @mask: bitfield mask
>> + * @shift: left shift
>> + */
>> +struct stm32_adc_regs {
>> + int reg;
>> + int mask;
>> + int shift;
>> +};
>> +
>> +/**
>> + * stm32_adc_trig_reginfo - stm32 ADC trigger control registers description
>> + * @reg: trigger control register offset (exten/jexten)
>> + * @exten_mask: external trigger en/polarity mask in @reg
>> + * @exten_shift: external trigger en/polarity shift in @reg
>> + * @extsel_mask: external trigger source mask in @reg
>> + * @extsel_shift: external trigger source shift in @reg
>> + */
>> +struct stm32_adc_trig_reginfo {
>> + u32 reg;
>> + u32 exten_mask;
>> + u32 exten_shift;
>> + u32 extsel_mask;
>> + u32 extsel_shift;
>> +};
>> +
>> +/**
>> + * struct stm32_adc_reginfo - stm32 ADC registers description
>> + * @isr: interrupt status register offset
>> + * @eoc: end of conversion mask in @isr
>> + * @jeoc: end of injected conversion sequence mask in @isr
>> + * @ier: interrupt enable register offset
>> + * @eocie: end of conversion interrupt enable mask in @ier
>> + * @jeocie: end of injected conversion sequence interrupt en mask
>> + * @dr: data register offset
>> + * @jdr: injected data registers offsets
>> + * @sqr_regs: Regular sequence registers description
>> + * @jsqr_reg: Injected sequence register description
>> + * @trig_reginfo: regular trigger control registers description
>> + * @jtrig_reginfo: injected trigger control registers description
>> + */
>> +struct stm32_adc_reginfo {
>> + u32 isr;
>> + u32 eoc;
>> + u32 jeoc;
>> + u32 ier;
>> + u32 eocie;
>> + u32 jeocie;
>> + u32 dr;
>> + u32 jdr[4];
>> + const struct stm32_adc_regs *sqr_regs;
>> + const struct stm32_adc_regs *jsqr_reg;
>> + const struct stm32_adc_trig_reginfo *trig_reginfo;
>> + const struct stm32_adc_trig_reginfo *jtrig_reginfo;
>> +};
>> +
>> +struct stm32_adc;
>> +
>> +/**
>> + * struct stm32_adc_ops - stm32 ADC, compatible dependent data
>> + * - stm32 ADC may work as single ADC, or as tightly coupled master/slave ADCs.
>> + *
>> + * @adc_info: Array spec for stm32 adc master/slaves instances
>> + * @ext_triggers: Reference to trigger info for regular channels
>> + * @jext_triggers: Reference to trigger info for injected channels
>> + * @adc_reginfo: stm32 ADC registers description
>> + * @highres: Max resolution
>> + * @max_clock_rate: Max input clock rate
>> + * @clk_sel: routine to select common clock and prescaler
>> + * @start_conv: routine to start conversions
>> + * @stop_conv: routine to stop conversions
>> + * @is_started: routine to get adc 'started' state
>> + * @regular_started routine to check regular conversions status
>> + * @injected_started routine to check injected conversions status
>> + * @enable: optional routine to enable stm32 adc
>> + * @disable: optional routine to disable stm32 adc
>> + * @is_enabled reports enabled state
>> + */
> This is a big chunk of abstraction that seems excessive at the moment.
> I'd rather see it introduced only just before it's actually used..
> (I'm guessing it's intended for support of similar parts?)
>
> Right now it just makes the driver harder to review.
>> +struct stm32_adc_ops {
>> + const struct stm32_adc_info *adc_info;
>> + const struct stm32_adc_trig_info *ext_triggers;
>> + const struct stm32_adc_trig_info *jext_triggers;
>> + const struct stm32_adc_reginfo *adc_reginfo;
>> + int highres;
>> + unsigned long max_clock_rate;
>> + int (*clk_sel)(struct stm32_adc *adc);
>> + int (*start_conv)(struct stm32_adc *adc);
>> + int (*stop_conv)(struct stm32_adc *adc);
>> + bool (*is_started)(struct stm32_adc *adc);
>> + bool (*regular_started)(struct stm32_adc *adc);
>> + bool (*injected_started)(struct stm32_adc *adc);
>> + int (*enable)(struct stm32_adc *adc);
>> + void (*disable)(struct stm32_adc *adc);
>> + bool (*is_enabled)(struct stm32_adc *adc);
>> +};
>> +
>> +struct stm32_adc_common;
>> +
>> +/**
>> + * struct stm32_adc - private data of each ADC IIO instance
>> + * @common: reference to ADC block common data
>> + * @adc_list: current ADC entry in common ADC list
>> + * @id: ADC instance number (e.g. adc 1, 2 or 3)
>> + * @offset: ADC instance register offset in ADC block
>> + * @max_channels: Max channels number for this ADC.
>> + * @extrig_list: External trigger list (for regular channel)
>> + * @completion: end of single conversion completion
>> + * @buffer: data buffer
>> + * @bufi: data buffer index
>> + * @num_conv: expected number of scan conversions
>> + * @injected: use injected channels on this adc
>> + * @lock: spinlock
>> + * @clk: optional adc clock, for this adc instance
>> + * @calib: optional calibration data
>> + * @en: emulates enabled state on some stm32 adc
>> + */
>> +struct stm32_adc {
>> + struct stm32_adc_common *common;
>> + struct list_head adc_list;
>> + int id;
>> + int offset;
>> + int max_channels;
>> + struct list_head extrig_list;
>> + struct completion completion;
>> + u16 *buffer;
>> + int bufi;
>> + int num_conv;
>> + bool injected;
>> + spinlock_t lock; /* interrupt lock */
>> + struct clk *clk;
>> + void *calib;
>> + bool en;
>> +};
>> +
>> +/**
>> + * struct stm32_adc_common - private data of ADC driver, common to all
>> + * ADC instances (ADC block)
>> + * @dev: device for this controller
>> + * @base: control registers base cpu addr
>> + * @irq: Common irq line for all adc instances
>> + * @data: STM32 dependent data from compatible
>> + * @adc_list: list of all stm32 ADC in this ADC block
>> + * @aclk: common clock for the analog circuitry
>> + * @vref: regulator reference
>> + * @vref_mv: vref voltage (mv)
>> + * @lock: mutex
>> + */
>> +struct stm32_adc_common {
>> + struct device *dev;
>> + void __iomem *base;
>> + int irq;
>> + const struct stm32_adc_ops *data;
>> + struct list_head adc_list;
>> + struct clk *aclk;
>> + struct regulator *vref;
>> + int vref_mv;
>> + struct mutex lock; /* read_raw lock */
>> +};
>> +
>> +/* Helper routines */
>> +static inline int stm32_adc_start_conv(struct stm32_adc *adc)
>> +{
>> + return adc->common->data->start_conv(adc);
>> +}
>> +
>> +static inline int stm32_adc_stop_conv(struct stm32_adc *adc)
>> +{
>> + return adc->common->data->stop_conv(adc);
>> +}
>> +
>> +static inline bool stm32_adc_is_started(struct stm32_adc *adc)
>> +{
>> + return adc->common->data->is_started(adc);
>> +}
>> +
>> +static inline bool stm32_adc_regular_started(struct stm32_adc *adc)
>> +{
>> + return adc->common->data->regular_started(adc);
>> +}
>> +
>> +static inline bool stm32_adc_injected_started(struct stm32_adc *adc)
>> +{
>> + return adc->common->data->injected_started(adc);
>> +}
>> +
>> +static inline bool stm32_adc_clk_sel(struct stm32_adc *adc)
>> +{
>> + return adc->common->data->clk_sel(adc);
>> +}
>> +
>> +static inline int stm32_adc_enable(struct stm32_adc *adc)
>> +{
>> + if (adc->common->data->enable)
>> + return adc->common->data->enable(adc);
>> +
>> + adc->en = true;
>> +
>> + return 0;
>> +}
>> +
>> +static inline bool stm32_adc_is_enabled(struct stm32_adc *adc)
>> +{
>> + if (adc->common->data->is_enabled)
>> + return adc->common->data->is_enabled(adc);
>> + else
>> + return adc->en;
>> +}
>> +
>> +static inline void stm32_adc_disable(struct stm32_adc *adc)
>> +{
>> + /* Check there is no regular or injected on-going conversions */
>> + if (stm32_adc_is_started(adc))
>> + return;
>> +
>> + if (adc->common->data->disable)
>> + adc->common->data->disable(adc);
>> + else
>> + adc->en = false;
>> +}
>> +
>> +/* STM32 ADC registers access routines */
>> +static inline u32 stm32_adc_common_readl(struct stm32_adc_common *com, u32 reg)
>> +{
>> + u32 val = readl_relaxed(com->base + reg);
>> +
>> + return val;
>> +}
>> +
>> +static inline void stm32_adc_common_writel(struct stm32_adc_common *com,
>> + u32 reg, u32 val)
>> +{
>> + writel_relaxed(val, com->base + reg);
>> +}
>> +
>> +static inline u32 stm32_adc_readl(struct stm32_adc *adc, u32 reg)
>> +{
>> + u32 val = readl_relaxed(adc->common->base + adc->offset + reg);
>> +
>> + return val;
>> +}
>> +
>> +#define stm32_adc_readl_addr(addr) stm32_adc_readl(adc, addr)
>> +
>> +#define stm32_adc_readl_poll_timeout(reg, val, cond, sleep_us, timeout_us) \
>> + readx_poll_timeout(stm32_adc_readl_addr, reg, val, \
>> + cond, sleep_us, timeout_us)
>> +
>> +static inline void stm32_adc_writel(struct stm32_adc *adc, u32 reg, u32 val)
>> +{
>> + writel_relaxed(val, adc->common->base + adc->offset + reg);
>> +}
>> +
>> +static inline void stm32_adc_set_bits(struct stm32_adc *adc, u32 reg, u32 bits)
>> +{
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&adc->lock, flags);
>> + stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) | bits);
>> + spin_unlock_irqrestore(&adc->lock, flags);
>> +}
>> +
>> +static inline void stm32_adc_clr_bits(struct stm32_adc *adc, u32 reg, u32 bits)
>> +{
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&adc->lock, flags);
>> + stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) & ~bits);
>> + spin_unlock_irqrestore(&adc->lock, flags);
>> +}
>> +
>> +/* STM32 common extended attributes */
>> +extern const struct iio_enum stm32_adc_trig_pol;
>> +int stm32_adc_probe(struct platform_device *pdev);
>> +int stm32_adc_remove(struct platform_device *pdev);
>> +
>> +#endif
>> diff --git a/drivers/iio/adc/stm32/stm32f4-adc.c b/drivers/iio/adc/stm32/stm32f4-adc.c
>> new file mode 100644
>> index 0000000..147fe9c
>> --- /dev/null
>> +++ b/drivers/iio/adc/stm32/stm32f4-adc.c
>> @@ -0,0 +1,574 @@
>> +/*
>> + * This file is part of STM32F4 ADC driver
>> + *
>> + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved
>> + * Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
>> + *
>> + * License type: GPLv2
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms of the GNU General Public License version 2 as published by
>> + * the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful, but
>> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
>> + * or FITNESS FOR A PARTICULAR PURPOSE.
>> + * See the GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License along with
>> + * this program. If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/trigger.h>
>> +#include <linux/platform_device.h>
>> +#include "stm32-adc.h"
>> +
>> +/*
>> + * STM32F4 - ADC global register map
>> + * ________________________________________________________
>> + * | Offset | Register |
>> + * --------------------------------------------------------
>> + * | 0x000 | Master ADC1 |
>> + * --------------------------------------------------------
>> + * | 0x100 | Slave ADC2 |
>> + * --------------------------------------------------------
>> + * | 0x200 | Slave ADC3 |
>> + * --------------------------------------------------------
>> + * | 0x300 | Master & Slave common regs |
>> + * --------------------------------------------------------
>> + */
>> +
>> +/* STM32F4 - Registers for each ADC instance */
>> +#define STM32F4_ADCX_SR 0x00
>> +#define STM32F4_ADCX_CR1 0x04
>> +#define STM32F4_ADCX_CR2 0x08
>> +#define STM32F4_ADCX_SMPR1 0x0C
>> +#define STM32F4_ADCX_SMPR2 0x10
>> +#define STM32F4_ADCX_HTR 0x24
>> +#define STM32F4_ADCX_LTR 0x28
>> +#define STM32F4_ADCX_SQR1 0x2C
>> +#define STM32F4_ADCX_SQR2 0x30
>> +#define STM32F4_ADCX_SQR3 0x34
>> +#define STM32F4_ADCX_JSQR 0x38
>> +#define STM32F4_ADCX_JDR1 0x3C
>> +#define STM32F4_ADCX_JDR2 0x40
>> +#define STM32F4_ADCX_JDR3 0x44
>> +#define STM32F4_ADCX_JDR4 0x48
>> +#define STM32F4_ADCX_DR 0x4C
>> +
>> +/* STM32 - Master & slave registers (common for all instances: 1, 2 & 3) */
>> +#define STM32F4_ADC_CSR (STM32_ADCX_COMN_OFFSET + 0x00)
>> +#define STM32F4_ADC_CCR (STM32_ADCX_COMN_OFFSET + 0x04)
>> +#define STM32F4_ADC_CDR (STM32_ADCX_COMN_OFFSET + 0x08)
>> +
>> +/* STM32F4_ADCX_SR - bit fields */
>> +#define STM32F4_OVR BIT(5)
>> +#define STM32F4_STRT BIT(4)
>> +#define STM32F4_JSTRT BIT(3)
>> +#define STM32F4_JEOC BIT(2)
>> +#define STM32F4_EOC BIT(1)
>> +#define STM32F4_AWD BIT(0)
>> +
>> +/* STM32F4_ADCX_CR1 - bit fields */
>> +#define STM32F4_OVRIE BIT(26)
>> +#define STM32F4_RES_SHIFT 24
>> +#define STM32F4_RES_MASK GENMASK(25, 24)
>> +#define STM32F4_AWDEN BIT(23)
>> +#define STM32F4_JAWDEN BIT(22)
>> +#define STM32F4_DISCNUM_SHIFT 13
>> +#define STM32F4_DISCNUM_MASK GENMASK(15, 13)
>> +#define STM32F4_JDISCEN BIT(12)
>> +#define STM32F4_DISCEN BIT(11)
>> +#define STM32F4_JAUTO BIT(10)
>> +#define STM32F4_AWDSGL BIT(9)
>> +#define STM32F4_SCAN BIT(8)
>> +#define STM32F4_JEOCIE BIT(7)
>> +#define STM32F4_AWDIE BIT(6)
>> +#define STM32F4_EOCIE BIT(5)
>> +#define STM32F4_AWDCH_SHIFT 0
>> +#define STM32F4_AWDCH_MASK GENMASK(4, 0)
>> +
>> +/* STM32F4_ADCX_CR2 - bit fields */
>> +#define STM32F4_SWSTART BIT(30)
>> +#define STM32F4_EXTEN_SHIFT 28
>> +#define STM32F4_EXTEN_MASK GENMASK(29, 28)
>> +#define STM32F4_EXTSEL_SHIFT 24
>> +#define STM32F4_EXTSEL_MASK GENMASK(27, 24)
>> +#define STM32F4_JSWSTART BIT(22)
>> +#define STM32F4_JEXTEN_SHIFT 20
>> +#define STM32F4_JEXTEN_MASK GENMASK(21, 20)
>> +#define STM32F4_JEXTSEL_SHIFT 16
>> +#define STM32F4_JEXTSEL_MASK GENMASK(19, 16)
>> +#define STM32F4_ALIGN BIT(11)
>> +#define STM32F4_EOCS BIT(10)
>> +#define STM32F4_DDS BIT(9)
>> +#define STM32F4_DMA BIT(8)
>> +#define STM32F4_CONT BIT(1)
>> +#define STM32F4_ADON BIT(0)
>> +
>> +/* STM32F4_ADCX_SMPR1 - bit fields */
>> +#define STM32F4_SMP18_SHIFT 24
>> +#define STM32F4_SMP18_MASK GENMASK(26, 24)
>> +#define STM32F4_SMP17_SHIFT 21
>> +#define STM32F4_SMP17_MASK GENMASK(23, 21)
>> +#define STM32F4_SMP16_SHIFT 18
>> +#define STM32F4_SMP16_MASK GENMASK(20, 18)
>> +#define STM32F4_SMP15_SHIFT 15
>> +#define STM32F4_SMP15_MASK GENMASK(17, 15)
>> +#define STM32F4_SMP14_SHIFT 12
>> +#define STM32F4_SMP14_MASK GENMASK(14, 12)
>> +#define STM32F4_SMP13_SHIFT 9
>> +#define STM32F4_SMP13_MASK GENMASK(11, 9)
>> +#define STM32F4_SMP12_SHIFT 6
>> +#define STM32F4_SMP12_MASK GENMASK(8, 6)
>> +#define STM32F4_SMP11_SHIFT 3
>> +#define STM32F4_SMP11_MASK GENMASK(5, 3)
>> +#define STM32F4_SMP10_SHIFT 0
>> +#define STM32F4_SMP10_MASK GENMASK(2, 0)
>> +
>> +/* STM32F4_ADCX_SMPR2 - bit fields */
>> +#define STM32F4_SMP9_SHIFT 27
>> +#define STM32F4_SMP9_MASK GENMASK(29, 27)
>> +#define STM32F4_SMP8_SHIFT 24
>> +#define STM32F4_SMP8_MASK GENMASK(26, 24)
>> +#define STM32F4_SMP7_SHIFT 21
>> +#define STM32F4_SMP7_MASK GENMASK(23, 21)
>> +#define STM32F4_SMP6_SHIFT 18
>> +#define STM32F4_SMP6_MASK GENMASK(20, 18)
>> +#define STM32F4_SMP5_SHIFT 15
>> +#define STM32F4_SMP5_MASK GENMASK(17, 15)
>> +#define STM32F4_SMP4_SHIFT 12
>> +#define STM32F4_SMP4_MASK GENMASK(14, 12)
>> +#define STM32F4_SMP3_SHIFT 9
>> +#define STM32F4_SMP3_MASK GENMASK(11, 9)
>> +#define STM32F4_SMP2_SHIFT 6
>> +#define STM32F4_SMP2_MASK GENMASK(8, 6)
>> +#define STM32F4_SMP1_SHIFT 3
>> +#define STM32F4_SMP1_MASK GENMASK(5, 3)
>> +#define STM32F4_SMP0_SHIFT 0
>> +#define STM32F4_SMP0_MASK GENMASK(2, 0)
>> +enum stm32f4_adc_smpr {
>> + STM32F4_SMPR_3_CK_CYCLES,
>> + STM32F4_SMPR_15_CK_CYCLES,
>> + STM32F4_SMPR_28_CK_CYCLES,
>> + STM32F4_SMPR_56_CK_CYCLES,
>> + STM32F4_SMPR_84_CK_CYCLES,
>> + STM32F4_SMPR_112_CK_CYCLES,
>> + STM32F4_SMPR_144_CK_CYCLES,
>> + STM32F4_SMPR_480_CK_CYCLES,
>> +};
>> +
>> +/* STM32F4_ADCX_SQR1 - bit fields */
>> +#define STM32F4_L_SHIFT 20
>> +#define STM32F4_L_MASK GENMASK(23, 20)
>> +#define STM32F4_SQ16_SHIFT 15
>> +#define STM32F4_SQ16_MASK GENMASK(19, 15)
>> +#define STM32F4_SQ15_SHIFT 10
>> +#define STM32F4_SQ15_MASK GENMASK(14, 10)
>> +#define STM32F4_SQ14_SHIFT 5
>> +#define STM32F4_SQ14_MASK GENMASK(9, 5)
>> +#define STM32F4_SQ13_SHIFT 0
>> +#define STM32F4_SQ13_MASK GENMASK(4, 0)
>> +
>> +/* STM32F4_ADCX_SQR2 - bit fields */
>> +#define STM32F4_SQ12_SHIFT 25
>> +#define STM32F4_SQ12_MASK GENMASK(29, 25)
>> +#define STM32F4_SQ11_SHIFT 20
>> +#define STM32F4_SQ11_MASK GENMASK(24, 20)
>> +#define STM32F4_SQ10_SHIFT 15
>> +#define STM32F4_SQ10_MASK GENMASK(19, 15)
>> +#define STM32F4_SQ9_SHIFT 10
>> +#define STM32F4_SQ9_MASK GENMASK(14, 10)
>> +#define STM32F4_SQ8_SHIFT 5
>> +#define STM32F4_SQ8_MASK GENMASK(9, 5)
>> +#define STM32F4_SQ7_SHIFT 0
>> +#define STM32F4_SQ7_MASK GENMASK(4, 0)
>> +
>> +/* STM32F4_ADCX_SQR3 - bit fields */
>> +#define STM32F4_SQ6_SHIFT 25
>> +#define STM32F4_SQ6_MASK GENMASK(29, 25)
>> +#define STM32F4_SQ5_SHIFT 20
>> +#define STM32F4_SQ5_MASK GENMASK(24, 20)
>> +#define STM32F4_SQ4_SHIFT 15
>> +#define STM32F4_SQ4_MASK GENMASK(19, 15)
>> +#define STM32F4_SQ3_SHIFT 10
>> +#define STM32F4_SQ3_MASK GENMASK(14, 10)
>> +#define STM32F4_SQ2_SHIFT 5
>> +#define STM32F4_SQ2_MASK GENMASK(9, 5)
>> +#define STM32F4_SQ1_SHIFT 0
>> +#define STM32F4_SQ1_MASK GENMASK(4, 0)
>> +
>> +/* STM32F4_ADCX_JSQR - bit fields */
>> +#define STM32F4_JL_SHIFT 20
>> +#define STM32F4_JL_MASK GENMASK(21, 20)
>> +#define STM32F4_JSQ4_SHIFT 15
>> +#define STM32F4_JSQ4_MASK GENMASK(19, 15)
>> +#define STM32F4_JSQ3_SHIFT 10
>> +#define STM32F4_JSQ3_MASK GENMASK(14, 10)
>> +#define STM32F4_JSQ2_SHIFT 5
>> +#define STM32F4_JSQ2_MASK GENMASK(9, 5)
>> +#define STM32F4_JSQ1_SHIFT 0
>> +#define STM32F4_JSQ1_MASK GENMASK(4, 0)
>> +
>> +/* STM32F4_ADC_CCR - bit fields */
>> +#define STM32F4_ADC_ADCPRE_SHIFT 16
>> +#define STM32F4_ADC_ADCPRE_MASK GENMASK(17, 16)
>> +
>> +/*
>> + * stm32 ADC1, ADC2 & ADC3 are tightly coupled and may be used in multi mode
>> + * Define here all inputs for all ADC instances
>> + */
>> +static const struct stm32_adc_chan_spec stm32f4_adc1_channels[] = {
>> + /* master ADC1 */
>> + { IIO_VOLTAGE, 0, "in0" },
>> + { IIO_VOLTAGE, 1, "in1" },
>> + { IIO_VOLTAGE, 2, "in2" },
>> + { IIO_VOLTAGE, 3, "in3" },
>> + { IIO_VOLTAGE, 4, "in4" },
>> + { IIO_VOLTAGE, 5, "in5" },
>> + { IIO_VOLTAGE, 6, "in6" },
>> + { IIO_VOLTAGE, 7, "in7" },
>> + { IIO_VOLTAGE, 8, "in8" },
>> + { IIO_VOLTAGE, 9, "in9" },
>> + { IIO_VOLTAGE, 10, "in10" },
>> + { IIO_VOLTAGE, 11, "in11" },
>> + { IIO_VOLTAGE, 12, "in12" },
>> + { IIO_VOLTAGE, 13, "in13" },
>> + { IIO_VOLTAGE, 14, "in14" },
>> + { IIO_VOLTAGE, 15, "in15" },
>> + /* internal analog sources available on input 16 to 18 */
>> + { IIO_VOLTAGE, 16, "in16" },
>> + { IIO_VOLTAGE, 17, "in17" },
>> + { IIO_VOLTAGE, 18, "in18" },
>> +};
>> +
>> +static const struct stm32_adc_chan_spec stm32f4_adc23_channels[] = {
>> + /* slave ADC2 / ADC3 */
>> + { IIO_VOLTAGE, 0, "in0" },
>> + { IIO_VOLTAGE, 1, "in1" },
>> + { IIO_VOLTAGE, 2, "in2" },
>> + { IIO_VOLTAGE, 3, "in3" },
>> + { IIO_VOLTAGE, 4, "in4" },
>> + { IIO_VOLTAGE, 5, "in5" },
>> + { IIO_VOLTAGE, 6, "in6" },
>> + { IIO_VOLTAGE, 7, "in7" },
>> + { IIO_VOLTAGE, 8, "in8" },
>> + { IIO_VOLTAGE, 9, "in9" },
>> + { IIO_VOLTAGE, 10, "in10" },
>> + { IIO_VOLTAGE, 11, "in11" },
>> + { IIO_VOLTAGE, 12, "in12" },
>> + { IIO_VOLTAGE, 13, "in13" },
>> + { IIO_VOLTAGE, 14, "in14" },
>> + { IIO_VOLTAGE, 15, "in15" },
>> +};
>> +
>> +/* Triggers for regular channels */
>> +static const struct stm32_adc_trig_info stm32f4_adc_ext_triggers[] = {
>> + { STM32_EXT0, "TIM1_CH1" },
>> + { STM32_EXT1, "TIM1_CH2" },
>> + { STM32_EXT2, "TIM1_CH3" },
>> + { STM32_EXT3, "TIM2_CH2" },
>> + { STM32_EXT4, "TIM2_CH3" },
>> + { STM32_EXT5, "TIM2_CH4" },
>> + { STM32_EXT6, "TIM2_TRGO" },
>> + { STM32_EXT7, "TIM3_CH1" },
>> + { STM32_EXT8, "TIM3_TRGO" },
>> + { STM32_EXT9, "TIM4_CH4" },
>> + { STM32_EXT10, "TIM5_CH1" },
>> + { STM32_EXT11, "TIM5_CH2" },
>> + { STM32_EXT12, "TIM5_CH3" },
>> + { STM32_EXT13, "TIM8_CH1" },
>> + { STM32_EXT14, "TIM8_TRGO" },
>> + { STM32_EXT15, "EXTI_11" },
>> + {},
>> +};
>> +
>> +/* Triggers for injected channels */
>> +static const struct stm32_adc_trig_info stm32f4_adc_jext_triggers[] = {
>> + { STM32_JEXT0, "TIM1_CH4" },
>> + { STM32_JEXT1, "TIM1_TRGO" },
>> + { STM32_JEXT2, "TIM2_CH1" },
>> + { STM32_JEXT3, "TIM2_TRGO" },
>> + { STM32_JEXT4, "TIM3_CH2" },
>> + { STM32_JEXT5, "TIM3_CH4" },
>> + { STM32_JEXT6, "TIM4_CH1" },
>> + { STM32_JEXT7, "TIM4_CH2" },
>> + { STM32_JEXT8, "TIM4_CH3" },
>> + { STM32_JEXT9, "TIM4_TRGO" },
>> + { STM32_JEXT10, "TIM5_CH4" },
>> + { STM32_JEXT11, "TIM5_TRGO" },
>> + { STM32_JEXT12, "TIM8_CH2" },
>> + { STM32_JEXT13, "TIM8_CH3" },
>> + { STM32_JEXT14, "TIM8_CH4" },
>> + { STM32_JEXT15, "EXTI_15" },
>> + {},
>> +};
>> +
>> +static const struct stm32_adc_info stm32f4_adc_info[] = {
>> + {
>> + .name = "adc1-master",
>> + .reg = 0x0,
>> + .channels = stm32f4_adc1_channels,
>> + .max_channels = ARRAY_SIZE(stm32f4_adc1_channels),
>> + },
>> + {
>> + .name = "adc2-slave",
>> + .reg = 0x100,
>> + .channels = stm32f4_adc23_channels,
>> + .max_channels = ARRAY_SIZE(stm32f4_adc23_channels),
>> + },
>> + {
>> + .name = "adc3-slave",
>> + .reg = 0x200,
>> + .channels = stm32f4_adc23_channels,
>> + .max_channels = ARRAY_SIZE(stm32f4_adc23_channels),
>> + },
>> + {},
>> +};
>> +
>> +/**
>> + * stm32f4_sqr_regs - describe regular sequence registers
>> + * - L: sequence len (register & bit field)
>> + * - SQ1..SQ16: sequence entries (register & bit field)
>> + */
>> +static const struct stm32_adc_regs stm32f4_sqr_regs[STM32_ADC_MAX_SQ + 1] = {
>> + /* L: len bit field description to be kept as first element */
>> + { STM32F4_ADCX_SQR1, STM32F4_L_MASK, STM32F4_L_SHIFT },
>> + /* SQ1..SQ16 registers & bit fields */
>> + { STM32F4_ADCX_SQR3, STM32F4_SQ1_MASK, STM32F4_SQ1_SHIFT },
>> + { STM32F4_ADCX_SQR3, STM32F4_SQ2_MASK, STM32F4_SQ2_SHIFT },
>> + { STM32F4_ADCX_SQR3, STM32F4_SQ3_MASK, STM32F4_SQ3_SHIFT },
>> + { STM32F4_ADCX_SQR3, STM32F4_SQ4_MASK, STM32F4_SQ4_SHIFT },
>> + { STM32F4_ADCX_SQR3, STM32F4_SQ5_MASK, STM32F4_SQ5_SHIFT },
>> + { STM32F4_ADCX_SQR3, STM32F4_SQ6_MASK, STM32F4_SQ6_SHIFT },
>> + { STM32F4_ADCX_SQR2, STM32F4_SQ7_MASK, STM32F4_SQ7_SHIFT },
>> + { STM32F4_ADCX_SQR2, STM32F4_SQ8_MASK, STM32F4_SQ8_SHIFT },
>> + { STM32F4_ADCX_SQR2, STM32F4_SQ9_MASK, STM32F4_SQ9_SHIFT },
>> + { STM32F4_ADCX_SQR2, STM32F4_SQ10_MASK, STM32F4_SQ10_SHIFT },
>> + { STM32F4_ADCX_SQR2, STM32F4_SQ11_MASK, STM32F4_SQ11_SHIFT },
>> + { STM32F4_ADCX_SQR2, STM32F4_SQ12_MASK, STM32F4_SQ12_SHIFT },
>> + { STM32F4_ADCX_SQR1, STM32F4_SQ13_MASK, STM32F4_SQ13_SHIFT },
>> + { STM32F4_ADCX_SQR1, STM32F4_SQ14_MASK, STM32F4_SQ14_SHIFT },
>> + { STM32F4_ADCX_SQR1, STM32F4_SQ15_MASK, STM32F4_SQ15_SHIFT },
>> + { STM32F4_ADCX_SQR1, STM32F4_SQ16_MASK, STM32F4_SQ16_SHIFT },
>> +};
>> +
>> +/**
>> + * stm32f4_jsqr_reg - describe injected sequence register:
>> + * - JL: injected sequence len
>> + * - JSQ4..SQ1: sequence entries
>> + * When JL == 3, ADC converts JSQ1, JSQ2, JSQ3, JSQ4
>> + * When JL == 2, ADC converts JSQ2, JSQ3, JSQ4
>> + * When JL == 1, ADC converts JSQ3, JSQ4
>> + * When JL == 0, ADC converts JSQ4
>> + */
>> +static const struct stm32_adc_regs stm32f4_jsqr_reg[STM32_ADC_MAX_JSQ + 1] = {
>> + /* JL: len bit field description to be kept as first element */
>> + {STM32F4_ADCX_JSQR, STM32F4_JL_MASK, STM32F4_JL_SHIFT},
>> + /* JSQ4..JSQ1 registers & bit fields */
>> + {STM32F4_ADCX_JSQR, STM32F4_JSQ4_MASK, STM32F4_JSQ4_SHIFT},
>> + {STM32F4_ADCX_JSQR, STM32F4_JSQ3_MASK, STM32F4_JSQ3_SHIFT},
>> + {STM32F4_ADCX_JSQR, STM32F4_JSQ2_MASK, STM32F4_JSQ2_SHIFT},
>> + {STM32F4_ADCX_JSQR, STM32F4_JSQ1_MASK, STM32F4_JSQ1_SHIFT},
>> +};
>> +
>> +static const struct stm32_adc_trig_reginfo stm32f4_adc_trig_reginfo = {
>> + .reg = STM32F4_ADCX_CR2,
>> + .exten_mask = STM32F4_EXTEN_MASK,
>> + .exten_shift = STM32F4_EXTEN_SHIFT,
>> + .extsel_mask = STM32F4_EXTSEL_MASK,
>> + .extsel_shift = STM32F4_EXTSEL_SHIFT,
>> +};
>> +
>> +static const struct stm32_adc_trig_reginfo stm32f4_adc_jtrig_reginfo = {
>> + .reg = STM32F4_ADCX_CR2,
>> + .exten_mask = STM32F4_JEXTEN_MASK,
>> + .exten_shift = STM32F4_JEXTEN_SHIFT,
>> + .extsel_mask = STM32F4_JEXTSEL_MASK,
>> + .extsel_shift = STM32F4_JEXTSEL_SHIFT,
>> +};
>> +
>> +static const struct stm32_adc_reginfo stm32f4_adc_reginfo = {
>> + .isr = STM32F4_ADCX_SR,
>> + .eoc = STM32F4_EOC,
>> + .jeoc = STM32F4_JEOC,
>> + .ier = STM32F4_ADCX_CR1,
>> + .eocie = STM32F4_EOCIE,
>> + .jeocie = STM32F4_JEOCIE,
>> + .dr = STM32F4_ADCX_DR,
>> + .jdr = {
>> + STM32F4_ADCX_JDR1,
>> + STM32F4_ADCX_JDR2,
>> + STM32F4_ADCX_JDR3,
>> + STM32F4_ADCX_JDR4,
>> + },
>> + .sqr_regs = stm32f4_sqr_regs,
>> + .jsqr_reg = stm32f4_jsqr_reg,
>> + .trig_reginfo = &stm32f4_adc_trig_reginfo,
>> + .jtrig_reginfo = &stm32f4_adc_jtrig_reginfo,
>> +};
>> +
>> +static bool stm32f4_adc_is_started(struct stm32_adc *adc)
>> +{
>> + u32 val = stm32_adc_readl(adc, STM32F4_ADCX_CR2) & STM32F4_ADON;
>> +
>> + return !!val;
>> +}
>> +
>> +static bool stm32f4_adc_regular_started(struct stm32_adc *adc)
>> +{
>> + u32 val = stm32_adc_readl(adc, STM32F4_ADCX_SR) & STM32F4_STRT;
>> +
>> + return !!val;
>> +}
>> +
>> +static bool stm32f4_adc_injected_started(struct stm32_adc *adc)
>> +{
>> + u32 val = stm32_adc_readl(adc, STM32F4_ADCX_SR) & STM32F4_JSTRT;
>> +
>> + return !!val;
>> +}
>> +
>> +/**
>> + * stm32f4_adc_start_conv() - Start regular or injected conversions
>> + * @adc: stm32 adc instance
>> + *
>> + * Start single conversions for regular or injected channels.
>> + */
>> +static int stm32f4_adc_start_conv(struct stm32_adc *adc)
>> +{
>> + u32 trig_msk, start_msk;
>> +
>> + dev_dbg(adc->common->dev, "%s %s\n", __func__,
>> + adc->injected ? "injected" : "regular");
>> +
>> + stm32_adc_set_bits(adc, STM32F4_ADCX_CR1, STM32F4_SCAN);
>> +
>> + if (!stm32f4_adc_is_started(adc)) {
>> + stm32_adc_set_bits(adc, STM32F4_ADCX_CR2,
>> + STM32F4_EOCS | STM32F4_ADON);
>> +
>> + /* Wait for Power-up time (tSTAB from datasheet) */
>> + usleep_range(2, 3);
>> + }
>> +
>> + if (adc->injected) {
>> + trig_msk = STM32F4_JEXTEN_MASK;
>> + start_msk = STM32F4_JSWSTART;
>> + } else {
>> + trig_msk = STM32F4_EXTEN_MASK;
>> + start_msk = STM32F4_SWSTART;
>> + }
>> +
>> + /* Software start ? (e.g. trigger detection disabled ?) */
>> + if (!(stm32_adc_readl(adc, STM32F4_ADCX_CR2) & trig_msk))
>> + stm32_adc_set_bits(adc, STM32F4_ADCX_CR2, start_msk);
>> +
>> + return 0;
>> +}
>> +
>> +static int stm32f4_adc_stop_conv(struct stm32_adc *adc)
>> +{
>> + u32 val;
>> +
>> + dev_dbg(adc->common->dev, "%s %s\n", __func__,
>> + adc->injected ? "injected" : "regular");
>> +
>> + /* First disable trigger for either regular or injected channels */
>> + if (adc->injected) {
>> + stm32_adc_clr_bits(adc, STM32F4_ADCX_CR2, STM32F4_JEXTEN_MASK);
>> + stm32_adc_clr_bits(adc, STM32F4_ADCX_SR, STM32F4_JSTRT);
>> + } else {
>> + stm32_adc_clr_bits(adc, STM32F4_ADCX_CR2, STM32F4_EXTEN_MASK);
>> + stm32_adc_clr_bits(adc, STM32F4_ADCX_SR, STM32F4_STRT);
>> + }
>> +
>> + /* Disable adc when all triggered conversion have been disabled */
>> + val = stm32_adc_readl(adc, STM32F4_ADCX_CR2);
>> + val &= STM32F4_EXTEN_MASK | STM32F4_JEXTEN_MASK;
>> + if (!val) {
>> + stm32_adc_clr_bits(adc, STM32F4_ADCX_CR1, STM32F4_SCAN);
>> + stm32_adc_clr_bits(adc, STM32F4_ADCX_CR2, STM32F4_ADON);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/* ADC internal common clock prescaler division ratios */
>> +static int stm32f4_pclk_div[] = {2, 4, 6, 8};
>> +
>> +/**
>> + * stm32f4_adc_clk_sel() - Select ADC common clock prescaler
>> + * @adc: stm32 adc instance
>> + * Select clock prescaler used for analog conversions.
>> + */
>> +static int stm32f4_adc_clk_sel(struct stm32_adc *adc)
>> +{
>> + struct stm32_adc_common *common = adc->common;
>> + unsigned long rate;
>> + u32 val;
>> + int i;
>> +
>> + /* Common prescaler is set only once, when 1st ADC instance starts */
>> + list_for_each_entry(adc, &common->adc_list, adc_list)
>> + if (stm32f4_adc_is_started(adc))
>> + return 0;
>> +
>> + rate = clk_get_rate(common->aclk);
>> + for (i = 0; i < ARRAY_SIZE(stm32f4_pclk_div); i++) {
>> + if ((rate / stm32f4_pclk_div[i]) <=
>> + common->data->max_clock_rate)
>> + break;
>> + }
>> + if (i >= ARRAY_SIZE(stm32f4_pclk_div))
>> + return -EINVAL;
>> +
>> + val = stm32_adc_common_readl(common, STM32F4_ADC_CCR);
>> + val &= ~STM32F4_ADC_ADCPRE_MASK;
>> + val |= i << STM32F4_ADC_ADCPRE_SHIFT;
>> + stm32_adc_common_writel(common, STM32F4_ADC_CCR, val);
>> +
>> + dev_dbg(common->dev, "Using analog clock source at %ld kHz\n",
>> + rate / (stm32f4_pclk_div[i] * 1000));
>> +
>> + return 0;
>> +}
>> +
>> +static const struct stm32_adc_ops stm32f4_adc_ops = {
>> + .adc_info = stm32f4_adc_info,
>> + .ext_triggers = stm32f4_adc_ext_triggers,
>> + .jext_triggers = stm32f4_adc_jext_triggers,
>> + .adc_reginfo = &stm32f4_adc_reginfo,
>> + .highres = 12,
>> + .max_clock_rate = 36000000,
>> + .clk_sel = stm32f4_adc_clk_sel,
>> + .start_conv = stm32f4_adc_start_conv,
>> + .stop_conv = stm32f4_adc_stop_conv,
>> + .is_started = stm32f4_adc_is_started,
>> + .regular_started = stm32f4_adc_regular_started,
>> + .injected_started = stm32f4_adc_injected_started,
>> +};
>> +
>> +static const struct of_device_id stm32f4_adc_of_match[] = {
>> + { .compatible = "st,stm32f4-adc", .data = (void *)&stm32f4_adc_ops},
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(of, stm32f4_adc_of_match);
>> +
>> +static struct platform_driver stm32f4_adc_driver = {
>> + .probe = stm32_adc_probe,
>> + .remove = stm32_adc_remove,
>> + .driver = {
>> + .name = "stm32f4-adc",
>> + .of_match_table = stm32f4_adc_of_match,
>> + },
>> +};
>> +
>> +module_platform_driver(stm32f4_adc_driver);
>> +
>> +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
>> +MODULE_DESCRIPTION("STMicroelectronics STM32F4 ADC driver");
>> +MODULE_LICENSE("GPL v2");
>>
^ permalink raw reply
* [PATCH v2 01/14] ASoC: sun4i-codec: Move data structures to add create_card call to quirks
From: Maxime Ripard @ 2016-11-03 8:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-2-wens@csie.org>
On Thu, Nov 03, 2016 at 03:55:43PM +0800, Chen-Yu Tsai wrote:
> The audio codec on later Allwinner SoCs have a different layout and
> audio path compared to the A10/A20. However the PCM parts are still
> the same.
>
> The different layout and audio paths mean we need a different
> create_card function for different families, so they can create
> DAPM endpoint widgets and routes.
>
> This patch moves the regmap configs, quirks and of_device_id
> structures to just before the probe function, so we can, among other
> things, include a pointer for the create_card function. None of the
> lines of code were changed.
>
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161103/80d51ad8/attachment.sig>
^ permalink raw reply
* [PATCH 0/3] fix ohci phy name
From: Axel Haslam @ 2016-11-03 8:24 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161102124435.31777-1-ahaslam@baylibre.com>
Hi Sekhar, David,
It might make sense to have this patch series,
squashed into a single patch, would you agree,
or do you prefer it as is: one-per-subsystem?
Regards
Axel.
On Wed, Nov 2, 2016 at 1:44 PM, Axel Haslam <ahaslam@baylibre.com> wrote:
> The usb ohci clock match is not working because the usb clock
> is registered as "ohci" instead of "ohci.0"
>
> But since there is only a single ohci instance, lets pass -1 to
> the platform data id parameter and avoid the extra ".0" matching.
>
> while we are fixing this, rename the driver to "ohci-da8xx" to be
> consistent with davinci musb and other usb drivers.
>
> Axel Haslam (3):
> ARM: davinci: da8xx: Fix ohci driver name
> phy: da8xx-usb: rename the ohci device to ohci-da8xx
> usb: ohci-da8xx: rename driver to ohci-da8xx
>
> arch/arm/mach-davinci/da830.c | 2 +-
> arch/arm/mach-davinci/da850.c | 2 +-
> arch/arm/mach-davinci/da8xx-dt.c | 2 +-
> arch/arm/mach-davinci/usb-da8xx.c | 4 ++--
> drivers/phy/phy-da8xx-usb.c | 5 +++--
> drivers/usb/host/ohci-da8xx.c | 2 +-
> 6 files changed, 9 insertions(+), 8 deletions(-)
>
> --
> 2.10.1
>
^ permalink raw reply
* [PATCH v2 03/14] ASoC: sun4i-codec: Revise comments for register definition macros
From: Maxime Ripard @ 2016-11-03 8:24 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-4-wens@csie.org>
On Thu, Nov 03, 2016 at 03:55:45PM +0800, Chen-Yu Tsai wrote:
> This revises existing comments in the register definition macros
> section, and adds a few more, so that readers can clearly identify
> the types of control registers.
>
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161103/15ffa371/attachment.sig>
^ permalink raw reply
* [PATCH v2 04/14] ASoC: sun4i-codec: Increase DMA max burst to 8
From: Maxime Ripard @ 2016-11-03 8:26 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-5-wens@csie.org>
On Thu, Nov 03, 2016 at 03:55:46PM +0800, Chen-Yu Tsai wrote:
> According to the DMA engine API documentation, maxburst denotes the
> largest possible size of a single transfer, so as not to overflow
> destination FIFOs as explained in this excerpt from dmaengine.h
>
> * @src_maxburst: the maximum number of words (note: words, as in
> * units of the src_addr_width member, not bytes) that can be sent
> * in one burst to the device. Typically something like half the
> * FIFO depth on I/O peripherals so you don't overflow it. This
> * may or may not be applicable on memory sources.
> * @dst_maxburst: same as src_maxburst but for destination target
> * mutatis mutandis.
>
> The TX FIFO is 64 samples deep for stereo, and the RX FIFO is 16
> samples deep. So maxburst could be 32 and 8 for TX and RX respectively.
>
> Unfortunately the sunxi DMA controller driver takes maxburst as
> the requested burst size, rather than a limit, and returns an error
> for unsupported values. The original value was 4, but some later
> SoCs do not officially support this burst size.
>
> This patch increases maxburst on the TX side to 8, which is supported
> by all variants of the sunxi DMA controller.
>
> Cc: Vinod Koul <vinod.koul@intel.com>
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Thanks,
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161103/7e1feb49/attachment.sig>
^ permalink raw reply
* [PATCH v2 05/14] ASoC: sun4i-codec: Add support for optional reset control to quirks
From: Maxime Ripard @ 2016-11-03 8:26 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-6-wens@csie.org>
On Thu, Nov 03, 2016 at 03:55:47PM +0800, Chen-Yu Tsai wrote:
> The later Allwinner SoCs have a dedicated reset controller, and
> peripherals have dedicated reset controls which need to be deasserted
> before the associated peripheral can be used.
>
> Add support for this to the quirks structure and probe/remove functions.
>
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Thanks,
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161103/dcaed374/attachment.sig>
^ permalink raw reply
* [PATCH v2 07/14] ASoC: sun4i-codec: Add support for A31 Line In playback
From: Maxime Ripard @ 2016-11-03 8:29 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161103075556.29018-8-wens@csie.org>
On Thu, Nov 03, 2016 at 03:55:49PM +0800, Chen-Yu Tsai wrote:
> The A31 integrated codec has a stereo "Line In" input. Add support for
> it to the playback paths.
>
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Thanks,
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20161103/062cbad7/attachment.sig>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox