* [PATCH v2 02/10] clk: ccu-sun8i-a33: Add CLK_SET_RATE_PARENT to ac-dig
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
The audio DAI needs to set the clock rates of the ac-dig clock.
To make it possible, the parent PLL audio clock rates should
also be changed. This is possible via "CLK_SET_RATE_PARENT" flag.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
drivers/clk/sunxi-ng/ccu-sun8i-a33.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-a33.c b/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
index 9bd1f78a0547..3cd4190ccd59 100644
--- a/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-a33.c
@@ -440,7 +440,7 @@ static SUNXI_CCU_M_WITH_GATE(ve_clk, "ve", "pll-ve",
0x13c, 16, 3, BIT(31), CLK_SET_RATE_PARENT);
static SUNXI_CCU_GATE(ac_dig_clk, "ac-dig", "pll-audio",
- 0x140, BIT(31), 0);
+ 0x140, BIT(31), CLK_SET_RATE_PARENT);
static SUNXI_CCU_GATE(ac_dig_4x_clk, "ac-dig-4x", "pll-audio-4x",
0x140, BIT(30), 0);
static SUNXI_CCU_GATE(avs_clk, "avs", "osc24M",
--
2.11.0
^ permalink raw reply related
* [PATCH v2 03/10] dt-bindings: sound: Add new reset compatible for sun4i-i2s
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
Add a new compatible for sun4i-i2s driver to handle some
SoCs that have a reset line that must be asserted/deasserted.
This new compatible, "allwinner,sun6i-a31-i2s", requires two
properties:
- resets: phandle to the reset line
- reset-names: the name of the reset line ("rst").
Except these differences, the compatible is identical to previous one
which will not handle a reset line.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
.../devicetree/bindings/sound/sun4i-i2s.txt | 23 ++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/sun4i-i2s.txt b/Documentation/devicetree/bindings/sound/sun4i-i2s.txt
index 7a2c0945fd22..f673206e309b 100644
--- a/Documentation/devicetree/bindings/sound/sun4i-i2s.txt
+++ b/Documentation/devicetree/bindings/sound/sun4i-i2s.txt
@@ -7,6 +7,7 @@ Required properties:
- compatible: should be one of the following:
- "allwinner,sun4i-a10-i2s"
+ - "allwinner,sun6i-a31-i2s" for controller with reset lines
- reg: physical base address of the controller and length of memory mapped
region.
- interrupts: should contain the I2S interrupt.
@@ -19,7 +20,13 @@ Required properties:
- "mod" : module clock for the I2S controller
- #sound-dai-cells : Must be equal to 0
+Required properties for the following compatibles:
+ - "allwinner,sun6i-a31-i2s"
+- resets: phandle to the reset line for this codec
+- reset-names: Contains the reset signal name "rst"
+
Example:
+For "allwinner,sun4i-a10-i2s":
i2s0: i2s at 01c22400 {
#sound-dai-cells = <0>;
@@ -32,3 +39,19 @@ i2s0: i2s at 01c22400 {
<&dma SUN4I_DMA_NORMAL 3>;
dma-names = "rx", "tx";
};
+
+For "allwinner,sun6i-a31-i2s":
+
+dai: dai at 01c22c00 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun6i-a31-i2s";
+ reg = <0x01c22c00 0x200>;
+ interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_CODEC>, <&ccu CLK_AC_DIG>;
+ clock-names = "apb", "mod";
+ resets = <&ccu RST_BUS_CODEC>;
+ reset-names = "rst";
+ dmas = <&dma 15>, /* AUDIO_CODEC port */
+ <&dma 15>; /* AUDIO_CODEC port */
+ dma-names = "rx", "tx";
+};
--
2.11.0
^ permalink raw reply related
* [PATCH v2 04/10] ASoC: sun4i-i2s: Add quirks to handle new compatible for reset
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
Some SoCs have a reset line that must be asserted/deasserted.
This patch adds a quirk to handle the new compatible
"allwinner,sun6i-a31-i2s" which will deassert the reset
line on probe function and assert it on remove's one.
This new compatible is useful in case of A33 codec driver, for example.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
sound/soc/sunxi/sun4i-i2s.c | 57 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 55 insertions(+), 2 deletions(-)
diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c
index 4237323ef594..3635bbc72cbc 100644
--- a/sound/soc/sunxi/sun4i-i2s.c
+++ b/sound/soc/sunxi/sun4i-i2s.c
@@ -14,9 +14,11 @@
#include <linux/clk.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
+#include <linux/reset.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
@@ -92,6 +94,7 @@ struct sun4i_i2s {
struct clk *bus_clk;
struct clk *mod_clk;
struct regmap *regmap;
+ struct reset_control *rst;
unsigned int mclk_freq;
@@ -651,9 +654,22 @@ static int sun4i_i2s_runtime_suspend(struct device *dev)
return 0;
}
+struct sun4i_i2s_quirks {
+ bool has_reset;
+};
+
+static const struct sun4i_i2s_quirks sun4i_a10_i2s_quirks = {
+ .has_reset = false,
+};
+
+static const struct sun4i_i2s_quirks sun6i_a31_i2s_quirks = {
+ .has_reset = true,
+};
+
static int sun4i_i2s_probe(struct platform_device *pdev)
{
struct sun4i_i2s *i2s;
+ const struct sun4i_i2s_quirks *quirks;
struct resource *res;
void __iomem *regs;
int irq, ret;
@@ -674,6 +690,12 @@ static int sun4i_i2s_probe(struct platform_device *pdev)
return irq;
}
+ quirks = of_device_get_match_data(&pdev->dev);
+ if (!quirks) {
+ dev_err(&pdev->dev, "Failed to determine the quirks to use\n");
+ return -ENODEV;
+ }
+
i2s->bus_clk = devm_clk_get(&pdev->dev, "apb");
if (IS_ERR(i2s->bus_clk)) {
dev_err(&pdev->dev, "Can't get our bus clock\n");
@@ -692,7 +714,24 @@ static int sun4i_i2s_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "Can't get our mod clock\n");
return PTR_ERR(i2s->mod_clk);
}
-
+
+ if (quirks->has_reset) {
+ i2s->rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(i2s->rst)) {
+ dev_err(&pdev->dev, "Failed to get reset control\n");
+ return PTR_ERR(i2s->rst);
+ }
+ }
+
+ if (!IS_ERR(i2s->rst)) {
+ ret = reset_control_deassert(i2s->rst);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to deassert the reset control\n");
+ return -EINVAL;
+ }
+ }
+
i2s->playback_dma_data.addr = res->start + SUN4I_I2S_FIFO_TX_REG;
i2s->playback_dma_data.maxburst = 8;
@@ -727,23 +766,37 @@ static int sun4i_i2s_probe(struct platform_device *pdev)
sun4i_i2s_runtime_suspend(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
+ if (!IS_ERR(i2s->rst))
+ reset_control_assert(i2s->rst);
return ret;
}
static int sun4i_i2s_remove(struct platform_device *pdev)
{
+ struct sun4i_i2s *i2s = dev_get_drvdata(&pdev->dev);
+
snd_dmaengine_pcm_unregister(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
sun4i_i2s_runtime_suspend(&pdev->dev);
+ if (!IS_ERR(i2s->rst))
+ reset_control_assert(i2s->rst);
+
return 0;
}
static const struct of_device_id sun4i_i2s_match[] = {
- { .compatible = "allwinner,sun4i-a10-i2s", },
+ {
+ .compatible = "allwinner,sun4i-a10-i2s",
+ .data = &sun4i_a10_i2s_quirks,
+ },
+ {
+ .compatible = "allwinner,sun6i-a31-i2s",
+ .data = &sun6i_a31_i2s_quirks,
+ },
{}
};
MODULE_DEVICE_TABLE(of, sun4i_i2s_match);
--
2.11.0
^ permalink raw reply related
* [PATCH v2 05/10] ASoC: Add sun8i digital audio codec
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
Add the sun8i audio codec which handles the digital register of
A33 codec.
The driver handles only the basic playback from the DAC to headphones.
All other features (microphone, capture, etc) will be added later.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
sound/soc/sunxi/Kconfig | 11 +
sound/soc/sunxi/Makefile | 1 +
sound/soc/sunxi/sun8i-codec.c | 531 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 543 insertions(+)
create mode 100644 sound/soc/sunxi/sun8i-codec.c
diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
index 6c344e16aca4..13a8267f17c7 100644
--- a/sound/soc/sunxi/Kconfig
+++ b/sound/soc/sunxi/Kconfig
@@ -9,6 +9,17 @@ config SND_SUN4I_CODEC
Select Y or M to add support for the Codec embedded in the Allwinner
A10 and affiliated SoCs.
+config SND_SUN8I_CODEC
+ tristate "Allwinner SUN8I audio codec"
+ depends on OF
+ depends on MACH_SUN8I || COMPILE_TEST
+ select REGMAP_MMIO
+ help
+ This option enables the digital part of the internal audio codec for
+ Allwinner sun8i SoC (and particularly A33).
+
+ Say Y or M if you want to add sun8i digital audio codec support.
+
config SND_SUN8I_CODEC_ANALOG
tristate "Allwinner sun8i Codec Analog Controls Support"
depends on MACH_SUN8I || COMPILE_TEST
diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
index 241c0df9ca0c..1f1af6271731 100644
--- a/sound/soc/sunxi/Makefile
+++ b/sound/soc/sunxi/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o
obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o
+obj-$(CONFIG_SND_SUN8I_CODEC) += sun8i-codec.o
diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c
new file mode 100644
index 000000000000..66378c9e7794
--- /dev/null
+++ b/sound/soc/sunxi/sun8i-codec.c
@@ -0,0 +1,531 @@
+/*
+ * This driver supports the digital controls for the internal codec
+ * found in Allwinner's A33 SoCs.
+ *
+ * (C) Copyright 2010-2016
+ * Reuuimlla Technology Co., Ltd. <www.reuuimllatech.com>
+ * huangxin <huangxin@Reuuimllatech.com>
+ * Myl?ne Josserand <mylene.josserand@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#define SUN8I_SYSCLK_CTL 0x00c
+#define SUN8I_SYSCLK_CTL_AIF1CLK_ENA 11
+#define SUN8I_SYSCLK_CTL_AIF1CLK_SRC_PLL 9
+#define SUN8I_SYSCLK_CTL_AIF1CLK_SRC 8
+#define SUN8I_SYSCLK_CTL_SYSCLK_ENA 3
+#define SUN8I_SYSCLK_CTL_SYSCLK_SRC 0
+#define SUN8I_MOD_CLK_ENA 0x010
+#define SUN8I_MOD_CLK_ENA_AIF1 15
+#define SUN8I_MOD_CLK_ENA_DAC 2
+#define SUN8I_MOD_RST_CTL 0x014
+#define SUN8I_MOD_RST_CTL_AIF1 15
+#define SUN8I_MOD_RST_CTL_DAC 2
+#define SUN8I_SYS_SR_CTRL 0x018
+#define SUN8I_SYS_SR_CTRL_AIF1_FS 12
+#define SUN8I_SYS_SR_CTRL_AIF2_FS 8
+#define SUN8I_AIF1CLK_CTRL 0x040
+#define SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD 15
+#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV 14
+#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV 13
+#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV 9
+#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV 6
+#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_16 (1 << 6)
+#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ 4
+#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16 (1 << 4)
+#define SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT 2
+#define SUN8I_AIF1_DACDAT_CTRL 0x048
+#define SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA 15
+#define SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA 14
+#define SUN8I_DAC_DIG_CTRL 0x120
+#define SUN8I_DAC_DIG_CTRL_ENDA 15
+#define SUN8I_DAC_MXR_SRC 0x130
+#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA0L 15
+#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA1L 14
+#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF2DACL 13
+#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_ADCL 12
+#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA0R 11
+#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA1R 10
+#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF2DACR 9
+#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_ADCR 8
+
+#define SUN8I_SYS_SR_CTRL_AIF1_FS_MASK GENMASK(15, 12)
+#define SUN8I_SYS_SR_CTRL_AIF2_FS_MASK GENMASK(11, 8)
+#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK GENMASK(5, 4)
+#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK GENMASK(8, 6)
+
+struct sun8i_codec {
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *clk_module;
+ struct clk *clk_bus;
+};
+
+static int sun8i_codec_runtime_resume(struct device *dev)
+{
+ struct sun8i_codec *scodec = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(scodec->clk_module);
+ if (ret) {
+ dev_err(dev, "Failed to enable the module clock\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(scodec->clk_bus);
+ if (ret) {
+ dev_err(dev, "Failed to enable the bus clock\n");
+ goto err_disable_modclk;
+ }
+
+ regcache_cache_only(scodec->regmap, false);
+
+ ret = regcache_sync(scodec->regmap);
+ if (ret) {
+ dev_err(dev, "Failed to sync regmap cache\n");
+ goto err_disable_clk;
+ }
+
+ return 0;
+
+err_disable_clk:
+ clk_disable_unprepare(scodec->clk_bus);
+
+err_disable_modclk:
+ clk_disable_unprepare(scodec->clk_module);
+
+ return ret;
+}
+
+static int sun8i_codec_runtime_suspend(struct device *dev)
+{
+ struct sun8i_codec *scodec = dev_get_drvdata(dev);
+
+ regcache_cache_only(scodec->regmap, true);
+ regcache_mark_dirty(scodec->regmap);
+
+ clk_disable_unprepare(scodec->clk_module);
+ clk_disable_unprepare(scodec->clk_bus);
+
+ return 0;
+}
+
+static int sun8i_codec_get_hw_rate(struct snd_pcm_hw_params *params)
+{
+ unsigned int rate = params_rate(params);
+
+ switch (rate) {
+ case 8000:
+ case 7350:
+ return 0x0;
+ case 11025:
+ return 0x1;
+ case 12000:
+ return 0x2;
+ case 16000:
+ return 0x3;
+ case 22050:
+ return 0x4;
+ case 24000:
+ return 0x5;
+ case 32000:
+ return 0x6;
+ case 44100:
+ return 0x7;
+ case 48000:
+ return 0x8;
+ case 96000:
+ return 0x9;
+ case 192000:
+ return 0xa;
+ default:
+ return -EINVAL;
+ }
+}
+
+static u32 sun8i_codec_get_mod_freq(struct snd_pcm_hw_params *params)
+{
+ unsigned int rate = params_rate(params);
+
+ switch (rate) {
+ case 176400:
+ case 88200:
+ case 44100:
+ case 22050:
+ case 11025:
+ return 22579200;
+
+ case 192000:
+ case 128000:
+ case 96000:
+ case 64000:
+ case 48000:
+ case 32000:
+ case 24000:
+ case 16000:
+ case 12000:
+ case 8000:
+ return 24576000;
+
+ default:
+ return 0;
+ }
+}
+
+static int sun8i_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct sun8i_codec *scodec = snd_soc_codec_get_drvdata(dai->codec);
+ u32 value;
+
+ /* clock masters */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS: /* DAI Slave */
+ value = 0x0; /* Codec Master */
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM: /* DAI Master */
+ value = 0x1; /* Codec Slave */
+ break;
+ default:
+ return -EINVAL;
+ }
+ regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
+ BIT(SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD),
+ value << SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD);
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF: /* Normal */
+ value = 0x0;
+ break;
+ case SND_SOC_DAIFMT_IB_IF: /* Inversion */
+ value = 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
+ BIT(SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV),
+ value << SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV);
+ regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
+ BIT(SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV),
+ value << SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV);
+
+ /* DAI format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ value = 0x0;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ value = 0x1;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ value = 0x2;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ case SND_SOC_DAIFMT_DSP_B:
+ value = 0x3;
+ break;
+ default:
+ return -EINVAL;
+ }
+ regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
+ BIT(SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT),
+ value << SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT);
+
+ return 0;
+}
+
+static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct sun8i_codec *scodec = snd_soc_codec_get_drvdata(dai->codec);
+ u32 clk_freq;
+ int sample_rate, err;
+
+ /*
+ * The CPU DAI handles only a sample of 16 bits. Configure the
+ * codec to handle this type of sample resolution.
+ */
+ regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
+ SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK,
+ SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16);
+
+ regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL,
+ SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK,
+ SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_16);
+
+ sample_rate = sun8i_codec_get_hw_rate(params);
+ if (sample_rate < 0)
+ return sample_rate;
+
+ regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL,
+ SUN8I_SYS_SR_CTRL_AIF1_FS_MASK,
+ sample_rate << SUN8I_SYS_SR_CTRL_AIF1_FS);
+ regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL,
+ SUN8I_SYS_SR_CTRL_AIF2_FS_MASK,
+ sample_rate << SUN8I_SYS_SR_CTRL_AIF2_FS);
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new sun8i_output_left_mixer_controls[] = {
+ SOC_DAPM_SINGLE("LSlot 0", SUN8I_DAC_MXR_SRC,
+ SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA0L, 1, 0),
+ SOC_DAPM_SINGLE("LSlot 1", SUN8I_DAC_MXR_SRC,
+ SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA1L, 1, 0),
+ SOC_DAPM_SINGLE("DACL", SUN8I_DAC_MXR_SRC,
+ SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF2DACL, 1, 0),
+ SOC_DAPM_SINGLE("ADCL", SUN8I_DAC_MXR_SRC,
+ SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_ADCL, 1, 0),
+};
+
+static const struct snd_kcontrol_new sun8i_output_right_mixer_controls[] = {
+ SOC_DAPM_SINGLE("RSlot 0", SUN8I_DAC_MXR_SRC,
+ SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA0R, 1, 0),
+ SOC_DAPM_SINGLE("RSlot 1", SUN8I_DAC_MXR_SRC,
+ SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA1R, 1, 0),
+ SOC_DAPM_SINGLE("DACR", SUN8I_DAC_MXR_SRC,
+ SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF2DACR, 1, 0),
+ SOC_DAPM_SINGLE("ADCR", SUN8I_DAC_MXR_SRC,
+ SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_ADCR, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = {
+ /* Digital parts of the DACs */
+ SND_SOC_DAPM_SUPPLY("DAC", SUN8I_DAC_DIG_CTRL, SUN8I_DAC_DIG_CTRL_ENDA,
+ 0, NULL, 0),
+
+ /* Analog DAC */
+ SND_SOC_DAPM_DAC("Digital Left DAC", "Playback", SUN8I_AIF1_DACDAT_CTRL,
+ SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA, 0),
+ SND_SOC_DAPM_DAC("Digital Right DAC", "Playback", SUN8I_AIF1_DACDAT_CTRL,
+ SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0),
+
+ /* DAC Mixers */
+ SND_SOC_DAPM_MIXER("Left DAC Mixer", SND_SOC_NOPM, 0, 0,
+ sun8i_output_left_mixer_controls,
+ ARRAY_SIZE(sun8i_output_left_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right DAC Mixer", SND_SOC_NOPM, 0, 0,
+ sun8i_output_right_mixer_controls,
+ ARRAY_SIZE(sun8i_output_right_mixer_controls)),
+
+ /* Clocks */
+ SND_SOC_DAPM_SUPPLY("MODCLK AFI1", SUN8I_MOD_CLK_ENA,
+ SUN8I_MOD_CLK_ENA_AIF1, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("MODCLK DAC", SUN8I_MOD_CLK_ENA,
+ SUN8I_MOD_CLK_ENA_DAC, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("AIF1", SUN8I_SYSCLK_CTL,
+ SUN8I_SYSCLK_CTL_AIF1CLK_ENA, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("SYSCLK", SUN8I_SYSCLK_CTL,
+ SUN8I_SYSCLK_CTL_SYSCLK_ENA, 0, NULL, 0),
+
+ SND_SOC_DAPM_SUPPLY("AIF1 PLL", SUN8I_SYSCLK_CTL,
+ SUN8I_SYSCLK_CTL_AIF1CLK_SRC_PLL, 0, NULL, 0),
+ /* Inversion as 0=AIF1, 1=AIF2 */
+ SND_SOC_DAPM_SUPPLY("SYSCLK AIF1", SUN8I_SYSCLK_CTL,
+ SUN8I_SYSCLK_CTL_SYSCLK_SRC, 1, NULL, 0),
+
+ /* Module reset */
+ SND_SOC_DAPM_SUPPLY("RST AIF1", SUN8I_MOD_RST_CTL,
+ SUN8I_MOD_RST_CTL_AIF1, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("RST DAC", SUN8I_MOD_RST_CTL,
+ SUN8I_MOD_RST_CTL_DAC, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("HP"),
+};
+
+static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = {
+ /* Clock Routes */
+ { "AIF1", NULL, "SYSCLK AIF1" },
+ { "AIF1 PLL", NULL, "AIF1" },
+ { "RST AIF1", NULL, "AIF1 PLL" },
+ { "MODCLK AFI1", NULL, "RST AIF1" },
+ { "DAC", NULL, "MODCLK AFI1" },
+
+ { "RST DAC", NULL, "SYSCLK" },
+ { "MODCLK DAC", NULL, "RST DAC" },
+ { "DAC", NULL, "MODCLK DAC" },
+
+ /* DAC Routes */
+ { "Digital Left DAC", NULL, "DAC" },
+ { "Digital Right DAC", NULL, "DAC" },
+
+ /* DAC Mixer Routes */
+ { "Left DAC Mixer", "LSlot 0", "Digital Left DAC"},
+ { "Right DAC Mixer", "RSlot 0", "Digital Right DAC"},
+
+ /* End of route : HP out */
+ { "HP", NULL, "Left DAC Mixer" },
+ { "HP", NULL, "Right DAC Mixer" },
+};
+
+static struct snd_soc_dai_ops sun8i_codec_dai_ops = {
+ .hw_params = sun8i_codec_hw_params,
+ .set_fmt = sun8i_set_fmt,
+};
+
+static struct snd_soc_dai_driver sun8i_codec_dai = {
+ .name = "sun8i",
+ /* playback capabilities */
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ /* pcm operations */
+ .ops = &sun8i_codec_dai_ops,
+};
+
+static struct snd_soc_codec_driver sun8i_soc_codec = {
+ .component_driver = {
+ .dapm_widgets = sun8i_codec_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_dapm_widgets),
+ .dapm_routes = sun8i_codec_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(sun8i_codec_dapm_routes),
+ },
+};
+
+static const struct regmap_config sun8i_codec_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = SUN8I_DAC_MXR_SRC,
+
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int sun8i_codec_probe(struct platform_device *pdev)
+{
+ struct resource *res_base;
+ struct sun8i_codec *scodec;
+ void __iomem *base;
+ int ret;
+
+ scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL);
+ if (!scodec)
+ return -ENOMEM;
+
+ scodec->dev = &pdev->dev;
+
+ scodec->clk_module = devm_clk_get(&pdev->dev, "mod");
+ if (IS_ERR(scodec->clk_module)) {
+ dev_err(&pdev->dev, "Failed to get the module clock\n");
+ return PTR_ERR(scodec->clk_module);
+ }
+
+ scodec->clk_bus = devm_clk_get(&pdev->dev, "bus");
+ if (IS_ERR(scodec->clk_bus)) {
+ dev_err(&pdev->dev, "Failed to get the bus clock\n");
+ return PTR_ERR(scodec->clk_bus);
+ }
+
+ res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(&pdev->dev, res_base);
+ if (IS_ERR(base)) {
+ dev_err(&pdev->dev, "Failed to map the registers\n");
+ return PTR_ERR(base);
+ }
+
+ scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &sun8i_codec_regmap_config);
+ if (IS_ERR(scodec->regmap)) {
+ dev_err(&pdev->dev, "Failed to create our regmap\n");
+ return PTR_ERR(scodec->regmap);
+ }
+
+ platform_set_drvdata(pdev, scodec);
+
+ pm_runtime_enable(&pdev->dev);
+ if (!pm_runtime_enabled(&pdev->dev)) {
+ ret = sun8i_codec_runtime_resume(&pdev->dev);
+ if (ret)
+ goto err_pm_disable;
+ }
+
+ ret = snd_soc_register_codec(&pdev->dev, &sun8i_soc_codec,
+ &sun8i_codec_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec\n");
+ goto err_suspend;
+ }
+
+ return ret;
+
+err_suspend:
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ sun8i_codec_runtime_suspend(&pdev->dev);
+
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static int sun8i_codec_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct sun8i_codec *scodec = snd_soc_card_get_drvdata(card);
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ sun8i_codec_runtime_suspend(&pdev->dev);
+
+ snd_soc_unregister_codec(&pdev->dev);
+ clk_disable_unprepare(scodec->clk_module);
+ clk_disable_unprepare(scodec->clk_bus);
+
+ return 0;
+}
+
+static const struct of_device_id sun8i_codec_of_match[] = {
+ { .compatible = "allwinner,sun8i-a33-codec" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sun8i_codec_of_match);
+
+static const struct dev_pm_ops sun8i_codec_pm_ops = {
+ SET_RUNTIME_PM_OPS(sun8i_codec_runtime_suspend,
+ sun8i_codec_runtime_resume, NULL)
+};
+
+static struct platform_driver sun8i_codec_driver = {
+ .driver = {
+ .name = "sun8i-codec",
+ .owner = THIS_MODULE,
+ .of_match_table = sun8i_codec_of_match,
+ .pm = &sun8i_codec_pm_ops,
+ },
+ .probe = sun8i_codec_probe,
+ .remove = sun8i_codec_remove,
+};
+module_platform_driver(sun8i_codec_driver);
+
+MODULE_DESCRIPTION("Allwinner A33 (sun8i) codec driver");
+MODULE_AUTHOR("Myl?ne Josserand <mylene.josserand@free-electrons.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sun8i-codec");
--
2.11.0
^ permalink raw reply related
* [PATCH v2 06/10] ASoC: sun8i-codec-analog: Add amplifier event to fix first delay
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
When playing a sound for the first time, a short delay, where the audio
file is not played, can be noticed.
On a second play (right after), the sound is played correctly.
If we wait a short time (~5 sec which corresponds to the aplay
timeout), the delay is back.
This patch fixes it by using an event on headphone amplifier.
It allows to keep the amplifier enable while playing a sound.
A delay of 700ms allows to wait that the amplifier is powered-up
before playing the sound.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
sound/soc/sunxi/sun8i-codec-analog.c | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c
index af02290ebe49..72331332b72e 100644
--- a/sound/soc/sunxi/sun8i-codec-analog.c
+++ b/sound/soc/sunxi/sun8i-codec-analog.c
@@ -398,11 +398,37 @@ static const struct snd_kcontrol_new sun8i_codec_hp_src[] = {
sun8i_codec_hp_src_enum),
};
+static int sun8i_headphone_amp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL,
+ BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN),
+ BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN));
+ /*
+ * Need a delay to have the amplifier up. 700ms seems the best
+ * compromise between the time to let the amplifier up and the
+ * time not to feel this delay while playing a sound.
+ */
+ msleep(700);
+ } else if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL,
+ BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN),
+ 0x0);
+ }
+
+ return 0;
+}
+
static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = {
SND_SOC_DAPM_MUX("Headphone Source Playback Route",
SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src),
- SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL,
- SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV_E("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL,
+ SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0,
+ sun8i_headphone_amp_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL,
SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0),
SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL,
--
2.11.0
^ permalink raw reply related
* [PATCH v2 07/10] dt-bindings: sound: Add sun8i audio documentation
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
Add the documentation for dt-binding of the digital audio codec driver
and the audio card driver for Sun8i SoCs.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
.../devicetree/bindings/sound/sun8i-codec.txt | 76 ++++++++++++++++++++++
1 file changed, 76 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/sun8i-codec.txt
diff --git a/Documentation/devicetree/bindings/sound/sun8i-codec.txt b/Documentation/devicetree/bindings/sound/sun8i-codec.txt
new file mode 100644
index 000000000000..ce3c05219e33
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/sun8i-codec.txt
@@ -0,0 +1,76 @@
+Allwinner SUN8I audio codec
+------------------------------------
+
+On Sun8i SoCs, and particularly on A33, the audio is separated in
+different parts:
+ - A DAI driver. It uses the "sun4i-i2s" driver which is
+ documented here:
+ Documentation/devicetree/bindings/sound/sun4i-i2s.txt
+ - An analog part of the codec which is handled as PRCM registers.
+ See Documentation/devicetree/bindings/sound/sun8i-codec-analog.txt
+ - An digital part of the codec which is documented in this current
+ binding documentation.
+ - And finally, an audio card which links all the above components.
+ The simple-audio card will be used.
+ See Documentation/devicetree/bindings/sound/simple-card.txt
+
+This bindings documentation exposes Sun8i codec (digital part).
+
+Required properties:
+- compatible: must be "allwinner,sun8i-a33-codec"
+- reg: must contain the registers location and length
+- interrupts: must contain the codec interrupt
+- clocks: a list of phandle + clock-specifer pairs, one for each entry
+ in clock-names.
+- clock-names: should contain followings:
+ - "bus": the parent APB clock for this controller
+ - "mod": the parent module clock
+
+Example:
+codec: codec at 01c22e00 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun8i-a33-codec";
+ reg = <0x01c22e00 0x400>;
+ interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_CODEC>, <&ccu CLK_AC_DIG>;
+ clock-names = "bus", "mod";
+ status = "disabled";
+};
+
+Here is an example to add a sound card and the codec binding on sun8i SoCs that
+are similar to A33 using simple-card:
+
+ sound {
+ compatible = "simple-audio-card";
+ simple-audio-card,name = "Sun8i Audio Card";
+ simple-audio-card,format = "i2s";
+ simple-audio-card,frame-master = <&link_codec>;
+ simple-audio-card,bitclock-master = <&link_codec>;
+ simple-audio-card,mclk-fs = <512>;
+ simple-audio-card,aux-devs = <&codec_analog>;
+ simple-audio-card,routing =
+ "Left DAC", "Digital Left DAC",
+ "Right DAC", "Digital Right DAC";
+
+ simple-audio-card,cpu {
+ sound-dai = <&dai>;
+ };
+
+ link_codec: simple-audio-card,codec {
+ sound-dai = <&codec>;
+ };
+
+ soc at 01c00000 {
+ [...]
+
+ codec: codec at 01c22e00 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun8i-a33-codec";
+ reg = <0x01c22e00 0x400>;
+ interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_CODEC>, <&ccu CLK_AC_DIG>;
+ clock-names = "bus", "mod";
+ status = "disabled";
+ };
+ };
+
--
2.11.0
^ permalink raw reply related
* [PATCH v2 08/10] ARM: dts: sun8i: Add audio codec, dai and card for A33
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
Add the audio codec, dai and a simple card to be able to use the
audio stream of the builtin codec on sun8i SoC.
This commit adds also an audio-routing for the sound card node to link
the analog DAPM widgets (Right/Left DAC) and the digital one's as they
are created in different drivers.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
arch/arm/boot/dts/sun8i-a33.dtsi | 47 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-a33.dtsi b/arch/arm/boot/dts/sun8i-a33.dtsi
index 63d5181ffff8..fe8a4f4760d2 100644
--- a/arch/arm/boot/dts/sun8i-a33.dtsi
+++ b/arch/arm/boot/dts/sun8i-a33.dtsi
@@ -43,6 +43,7 @@
*/
#include "sun8i-a23-a33.dtsi"
+#include <dt-bindings/dma/sun4i-a10.h>
/ {
cpus {
@@ -69,6 +70,27 @@
reg = <0x40000000 0x80000000>;
};
+ sound {
+ compatible = "simple-audio-card";
+ simple-audio-card,name = "Sun8i Audio Card";
+ simple-audio-card,format = "i2s";
+ simple-audio-card,frame-master = <&link_codec>;
+ simple-audio-card,bitclock-master = <&link_codec>;
+ simple-audio-card,mclk-fs = <512>;
+ simple-audio-card,aux-devs = <&codec_analog>;
+ simple-audio-card,routing =
+ "Left DAC", "Digital Left DAC",
+ "Right DAC", "Digital Right DAC";
+
+ simple-audio-card,cpu {
+ sound-dai = <&dai>;
+ };
+
+ link_codec: simple-audio-card,codec {
+ sound-dai = <&codec>;
+ };
+ };
+
soc at 01c00000 {
tcon0: lcd-controller at 01c0c000 {
compatible = "allwinner,sun8i-a33-tcon";
@@ -116,6 +138,31 @@
reset-names = "ahb";
};
+ dai: dai at 01c22c00 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun6i-a31-i2s";
+ reg = <0x01c22c00 0x200>;
+ interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_CODEC>, <&ccu CLK_AC_DIG>;
+ clock-names = "apb", "mod";
+ resets = <&ccu RST_BUS_CODEC>;
+ reset-names = "rst";
+ dmas = <&dma 15>, /* AUDIO_CODEC port */
+ <&dma 15>; /* AUDIO_CODEC port */
+ dma-names = "rx", "tx";
+ status = "disabled";
+ };
+
+ codec: codec at 01c22e00 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun8i-a33-codec";
+ reg = <0x01c22e00 0x400>;
+ interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_CODEC>, <&ccu CLK_AC_DIG>;
+ clock-names = "bus", "mod";
+ status = "disabled";
+ };
+
fe0: display-frontend at 01e00000 {
compatible = "allwinner,sun8i-a33-display-frontend";
reg = <0x01e00000 0x20000>;
--
2.11.0
^ permalink raw reply related
* [PATCH v2 09/10] ARM: dts: sun8i: parrot: Enable audio nodes
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
Enable the audio codec and the audio dai for the sun8i R16 Parrot board.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
arch/arm/boot/dts/sun8i-r16-parrot.dts | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-r16-parrot.dts b/arch/arm/boot/dts/sun8i-r16-parrot.dts
index 6e42adbde60b..7aa1b95f3b6e 100644
--- a/arch/arm/boot/dts/sun8i-r16-parrot.dts
+++ b/arch/arm/boot/dts/sun8i-r16-parrot.dts
@@ -84,6 +84,14 @@
};
+&codec {
+ status = "okay";
+};
+
+&dai {
+ status = "okay";
+};
+
&ehci0 {
status = "okay";
};
--
2.11.0
^ permalink raw reply related
* [PATCH v2 10/10] ARM: dts: sun8i: sinlinx: Enable audio nodes
From: Mylène Josserand @ 2017-01-17 14:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117140230.23142-1-mylene.josserand@free-electrons.com>
Enable the audio codec and the audio dai for the sun8i A33 sinlinx board.
Signed-off-by: Myl?ne Josserand <mylene.josserand@free-electrons.com>
---
arch/arm/boot/dts/sun8i-a33-sinlinx-sina33.dts | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-a33-sinlinx-sina33.dts b/arch/arm/boot/dts/sun8i-a33-sinlinx-sina33.dts
index 370ee82882de..b08811007f44 100644
--- a/arch/arm/boot/dts/sun8i-a33-sinlinx-sina33.dts
+++ b/arch/arm/boot/dts/sun8i-a33-sinlinx-sina33.dts
@@ -63,6 +63,14 @@
};
};
+&codec {
+ status = "okay";
+};
+
+&dai {
+ status = "okay";
+};
+
&ehci0 {
status = "okay";
};
--
2.11.0
^ permalink raw reply related
* [PATCH v8 14/18] irqdomain: irq_domain_check_msi_remap
From: Tomasz Nowicki @ 2017-01-17 14:06 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <f5aacc54-ca8e-cb09-461a-6105346b18b9@redhat.com>
On 17.01.2017 14:53, Auger Eric wrote:
> Hi Tomasz,
>
> On 17/01/2017 14:40, Tomasz Nowicki wrote:
>> On 11.01.2017 10:41, Eric Auger wrote:
>>> This new function checks whether all MSI irq domains
>>> implement IRQ remapping. This is useful to understand
>>> whether VFIO passthrough is safe with respect to interrupts.
>>>
>>> On ARM typically an MSI controller can sit downstream
>>> to the IOMMU without preventing VFIO passthrough.
>>> As such any assigned device can write into the MSI doorbell.
>>> In case the MSI controller implements IRQ remapping, assigned
>>> devices will not be able to trigger interrupts towards the
>>> host. On the contrary, the assignment must be emphasized as
>>> unsafe with respect to interrupts.
>>>
>>> Signed-off-by: Eric Auger <eric.auger@redhat.com>
>>> Reviewed-by: Marc Zyngier <marc.zyngier@arm.com>
>>>
>>> ---
>>> v7 -> v8:
>>> - remove goto in irq_domain_check_msi_remap
>>> - Added Marc's R-b
>>>
>>> v5 -> v6:
>>> - use irq_domain_hierarchical_is_msi_remap()
>>> - comment rewording
>>>
>>> v4 -> v5:
>>> - Handle DOMAIN_BUS_FSL_MC_MSI domains
>>> - Check parents
>>> ---
>>> include/linux/irqdomain.h | 1 +
>>> kernel/irq/irqdomain.c | 22 ++++++++++++++++++++++
>>> 2 files changed, 23 insertions(+)
>>>
>>> diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
>>> index bc2f571..188eced 100644
>>> --- a/include/linux/irqdomain.h
>>> +++ b/include/linux/irqdomain.h
>>> @@ -222,6 +222,7 @@ struct irq_domain *irq_domain_add_legacy(struct
>>> device_node *of_node,
>>> void *host_data);
>>> extern struct irq_domain *irq_find_matching_fwspec(struct irq_fwspec
>>> *fwspec,
>>> enum irq_domain_bus_token bus_token);
>>> +extern bool irq_domain_check_msi_remap(void);
>>> extern void irq_set_default_host(struct irq_domain *host);
>>> extern int irq_domain_alloc_descs(int virq, unsigned int nr_irqs,
>>> irq_hw_number_t hwirq, int node,
>>> diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
>>> index 876e131..d889751 100644
>>> --- a/kernel/irq/irqdomain.c
>>> +++ b/kernel/irq/irqdomain.c
>>> @@ -278,6 +278,28 @@ struct irq_domain
>>> *irq_find_matching_fwspec(struct irq_fwspec *fwspec,
>>> EXPORT_SYMBOL_GPL(irq_find_matching_fwspec);
>>>
>>> /**
>>> + * irq_domain_check_msi_remap - Check whether all MSI
>>> + * irq domains implement IRQ remapping
>>> + */
>>> +bool irq_domain_check_msi_remap(void)
>>> +{
>>> + struct irq_domain *h;
>>> + bool ret = true;
>>> +
>>> + mutex_lock(&irq_domain_mutex);
>>> + list_for_each_entry(h, &irq_domain_list, link) {
>>> + if (irq_domain_is_msi(h) &&
>>> + !irq_domain_hierarchical_is_msi_remap(h)) {
>>> + ret = false;
>>> + break;
>>> + }
>>> + }
>>> + mutex_unlock(&irq_domain_mutex);
>>> + return ret;
>>> +}
>>
>> Above function returns true, even though there is no MSI irq domains. Is
>> it intentional ?
> From the VFIO integration point of view this is what we want. If there
> is no MSI controller in the system, we have no vulnerability with
> respect to IRQ assignment and we consider the system as safe. If
> requested I can add a comment?
>
I see. Yes, a comment would be helpful then :) Thanks!
Tomasz
^ permalink raw reply
* [PATCH v4 3/4] arm64: dts: juno: add CoreSight support for Juno r1/r2 variants
From: Sudeep Holla @ 2017-01-17 14:07 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <d89b805b-cc6f-37d5-2b64-4f83fa086db6@arm.com>
On 17/01/17 13:27, Suzuki K Poulose wrote:
> On 17/01/17 12:15, Sudeep Holla wrote:
>> From: Mike Leach <mike.leach@linaro.org>
>>
>> The CoreSight support added for Juno is valid for only Juno r0.
>> The Juno r1 and r2 variants have additional components and alternative
>> connection routes between trace source and sinks.
>>
>> This patch builds on top of the existing r0 support and extends it to
>> Juno r1/r2 variants.
>>
>> Reviewed-by: Mathieu Poirier <mathieu.poirier@linaro.org>
>> Signed-off-by: Mike Leach <mike.leach@linaro.org>
>> [sudeep.holla at arm.com: minor changelog update and major reorganisation of
>> the common coresight components back into juno-base.dtsi to avoid
>> duplication, also renamed funnel node names]
>> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
>> ---
>> arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi | 100
>> ++++++++++++++++++++++++++++++
>> arch/arm64/boot/dts/arm/juno-r1.dts | 9 +++
>> arch/arm64/boot/dts/arm/juno-r2.dts | 9 +++
>> 3 files changed, 118 insertions(+)
>> create mode 100644 arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi
>>
>> diff --git a/arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi
>> b/arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi
>> new file mode 100644
>> index 000000000000..563463ed28c7
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi
>> @@ -0,0 +1,100 @@
>> +/ {
>> + funnel at 20130000 { /* cssys2 */
>
> Typo, that should be csys1. Rest looks good to me.
>
Fixed locally.
> Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Thanks again.
--
Regards,
Sudeep
^ permalink raw reply
* [PATCH 3/4] arm64: dts: r8a7795: Link ARM GIC to clock and clock domain
From: Geert Uytterhoeven @ 2017-01-17 14:08 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <HK2PR06MB05451777E7151CA9C7EF8990C37C0@HK2PR06MB0545.apcprd06.prod.outlook.com>
Hi Ramesh,
On Tue, Jan 17, 2017 at 3:00 PM, Ramesh Shanmugasundaram
<ramesh.shanmugasundaram@bp.renesas.com> wrote:
>> --- a/arch/arm64/boot/dts/renesas/r8a7795.dtsi
>> +++ b/arch/arm64/boot/dts/renesas/r8a7795.dtsi
>> @@ -166,6 +166,9 @@
>> <0x0 0xf1060000 0 0x20000>;
>> interrupts = <GIC_PPI 9
>> (GIC_CPU_MASK_SIMPLE(4) |
>> IRQ_TYPE_LEVEL_HIGH)>;
>> + clocks = <&cpg CPG_MOD 408>;
>> + clock-names = "clk";
>
> Should the module clock be named "fck"?
Not according to
Documentation/devicetree/bindings/interrupt-controller/arm,gic.txt.
P.S. If you use "git blame", you may think I'm cheating, but naming was
ased on the actual Technical Reference Manuals ;-)
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert at linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* [PATCH 1/2] arm64: dts: exynos: Add USB 3.0 controller node for Exynos7
From: pankaj.dubey @ 2017-01-17 14:09 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <3a607d18-acf9-37f5-a6d4-7f8fe538d27f@osg.samsung.com>
Hi Javier,
On Tuesday 17 January 2017 07:07 PM, Javier Martinez Canillas wrote:
> Hello Pankaj,
>
> On 01/17/2017 07:32 AM, Pankaj Dubey wrote:
>> Add USB 3.0 DRD controller device node, with its clock
>> and phy information to enable the same on Exynos7.
>>
>> Signed-off-by: Pankaj Dubey <pankaj.dubey@samsung.com>
>> Signed-off-by: Vivek Gautam <gautamvivek1987@gmail.com>
>
> The S-o-B order isn't correct. You are the one posting the patch so
> yours should be the last one.
>
> Also, wasn't this patch originally from Vivek? If you didn't change
> that much, I think that he should be listed as the patch author.
>
Thanks for review.
Just noticed, I missed to take care of this, as original patch could not
be applied on latest tree and I had to do all these changes manually,
will submit soon with Signed-off-by and author change.
Thanks,
Pankaj Dubey
> I don't have an Exynos7 manual to verify but the patch looks good.
>
> Reviewed-by: Javier Martinez Canillas <javier@osg.samsung.com>
>
> Best regards,
>
^ permalink raw reply
* [PATCH v8 11/18] iommu/arm-smmu-v3: Implement reserved region get/put callbacks
From: Tomasz Nowicki @ 2017-01-17 14:13 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1484127714-3263-12-git-send-email-eric.auger@redhat.com>
On 11.01.2017 10:41, Eric Auger wrote:
> iommu/arm-smmu: Implement reserved region get/put callbacks
>
> The get() populates the list with the MSI IOVA reserved window.
>
> At the moment an arbitray MSI IOVA window is set at 0x8000000
> of size 1MB. This will allow to report those info in iommu-group
> sysfs.
>
> Signed-off-by: Eric Auger <eric.auger@redhat.com>
> Acked-by: Will Deacon <will.deacon@arm.com>
Reviewed-by: Tomasz Nowicki <tomasz.nowicki@caviumnetworks.com>
Thanks,
Tomasz
>
> ---
>
> v7 -> v8:
> - added Will's A-b
>
> v4: creation
> ---
> drivers/iommu/arm-smmu-v3.c | 28 ++++++++++++++++++++++++++++
> 1 file changed, 28 insertions(+)
>
> diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
> index 4d6ec44..6c4111c 100644
> --- a/drivers/iommu/arm-smmu-v3.c
> +++ b/drivers/iommu/arm-smmu-v3.c
> @@ -412,6 +412,9 @@
> /* High-level queue structures */
> #define ARM_SMMU_POLL_TIMEOUT_US 100
>
> +#define MSI_IOVA_BASE 0x8000000
> +#define MSI_IOVA_LENGTH 0x100000
> +
> static bool disable_bypass;
> module_param_named(disable_bypass, disable_bypass, bool, S_IRUGO);
> MODULE_PARM_DESC(disable_bypass,
> @@ -1883,6 +1886,29 @@ static int arm_smmu_of_xlate(struct device *dev, struct of_phandle_args *args)
> return iommu_fwspec_add_ids(dev, args->args, 1);
> }
>
> +static void arm_smmu_get_resv_regions(struct device *dev,
> + struct list_head *head)
> +{
> + struct iommu_resv_region *region;
> + int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
> +
> + region = iommu_alloc_resv_region(MSI_IOVA_BASE, MSI_IOVA_LENGTH,
> + prot, IOMMU_RESV_MSI);
> + if (!region)
> + return;
> +
> + list_add_tail(®ion->list, head);
> +}
> +
> +static void arm_smmu_put_resv_regions(struct device *dev,
> + struct list_head *head)
> +{
> + struct iommu_resv_region *entry, *next;
> +
> + list_for_each_entry_safe(entry, next, head, list)
> + kfree(entry);
> +}
> +
> static struct iommu_ops arm_smmu_ops = {
> .capable = arm_smmu_capable,
> .domain_alloc = arm_smmu_domain_alloc,
> @@ -1898,6 +1924,8 @@ static int arm_smmu_of_xlate(struct device *dev, struct of_phandle_args *args)
> .domain_get_attr = arm_smmu_domain_get_attr,
> .domain_set_attr = arm_smmu_domain_set_attr,
> .of_xlate = arm_smmu_of_xlate,
> + .get_resv_regions = arm_smmu_get_resv_regions,
> + .put_resv_regions = arm_smmu_put_resv_regions,
> .pgsize_bitmap = -1UL, /* Restricted during device attach */
> };
>
>
^ permalink raw reply
* [PATCH -next] mtd: nand: mediatek: remove redundant dev_err call in mtk_nfc_probe()
From: Wei Yongjun @ 2017-01-17 14:22 UTC (permalink / raw)
To: linux-arm-kernel
From: Wei Yongjun <weiyongjun1@huawei.com>
There is a error message within devm_ioremap_resource
already, so remove the dev_err call to avoid redundant
error message.
Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
---
drivers/mtd/nand/mtk_nand.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
index 6c3eed3..6c517c6 100644
--- a/drivers/mtd/nand/mtk_nand.c
+++ b/drivers/mtd/nand/mtk_nand.c
@@ -1383,7 +1383,6 @@ static int mtk_nfc_probe(struct platform_device *pdev)
nfc->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(nfc->regs)) {
ret = PTR_ERR(nfc->regs);
- dev_err(dev, "no nfi base\n");
goto release_ecc;
}
^ permalink raw reply related
* [PATCH 1/3 v3] iio: adc: add device tree bindings for Qualcomm PM8xxx ADCs
From: Linus Walleij @ 2017-01-17 14:25 UTC (permalink / raw)
To: linux-arm-kernel
This adds the device tree bindings for the Qualcomm PM8xxx
ADCs. This is based on the existing DT bindings for the
SPMI ADC so there are hopefully no controversial features.
Cc: devicetree at vger.kernel.org
Cc: linux-arm-kernel at lists.infradead.org
Cc: linux-arm-msm at vger.kernel.org
Cc: Ivan T. Ivanov <iivanov.xz@gmail.com>
Cc: Andy Gross <andy.gross@linaro.org>
Cc: Bjorn Andersson <bjorn.andersson@linaro.org>
Cc: Stephen Boyd <sboyd@codeaurora.org>
Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Cc: Rama Krishna Phani A <rphani@codeaurora.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
ChangeLog v2->v3:
- Collapse the ratiometric property into one that enables and
selects a ratiometric reference in one go.
- Use foo: adc-channel at X notation consequently
- Document that the reference voltage is compulsory.
ChangeLog v1->v2:
- Spelling fixes
---
.../bindings/iio/adc/qcom,pm8xxx-xoadc.txt | 149 +++++++++++++++++++++
1 file changed, 149 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/qcom,pm8xxx-xoadc.txt
diff --git a/Documentation/devicetree/bindings/iio/adc/qcom,pm8xxx-xoadc.txt b/Documentation/devicetree/bindings/iio/adc/qcom,pm8xxx-xoadc.txt
new file mode 100644
index 000000000000..53cd146d8096
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/qcom,pm8xxx-xoadc.txt
@@ -0,0 +1,149 @@
+Qualcomm's PM8xxx voltage XOADC
+
+The Qualcomm PM8xxx PMICs contain a HK/XO ADC (Housekeeping/Crystal
+oscillator ADC) encompassing PM8018, PM8038, PM8058 and PM8921.
+
+Required properties:
+
+- compatible: should be one of:
+ "qcom,pm8018-adc"
+ "qcom,pm8038-adc"
+ "qcom,pm8058-adc"
+ "qcom,pm8921-adc"
+
+- reg: should contain the ADC base address in the PMIC, typically
+ 0x197.
+
+- xoadc-ref-supply: should reference a regulator that can supply
+ a reference voltage on demand. The reference voltage may vary
+ with PMIC variant but is typically something like 2.2 or 1.8V.
+
+The following required properties are standard for IO channels, see
+iio-bindings.txt for more details:
+
+- #address-cells: should be set to <1>
+
+- #size-cells: should be set to <0>
+
+- #io-channel-cells: should be set to <1>
+
+- interrupts: should refer to the parent PMIC interrupt controller
+ and reference the proper ADC interrupt.
+
+Required subnodes:
+
+The ADC channels are configured as subnodes of the ADC. Since some of
+them are used for calibrating the ADC, these nodes are compulsory:
+
+adc-channel at c {
+ reg = <0x0c>;
+};
+
+adc-channel at d {
+ reg = <0x0d>;
+};
+
+adc-channel at f {
+ reg = <0x0f>;
+};
+
+These three nodes are used for absolute and ratiometric calibration
+and only need to have these reg values: they are by hardware definition
+1:1 ratio converters that sample 625, 1250 and 0 milliV and create
+an interpolation calibration for all other ADCs.
+
+Optional subnodes: any channels other than channel 0x0c, 0x0d and
+0x0f are optional.
+
+Required channel node properties:
+
+- reg: should contain the hardware channel number in the range
+ 0 .. 0x0f (4 bits). The hardware only supports 16 channels.
+
+Optional channel node properties:
+
+- qcom,decimation:
+ Value type: <u32>
+ Definition: This parameter is used to decrease the ADC sampling rate.
+ Quicker measurements can be made by reducing the decimation ratio.
+ Valid values are 512, 1024, 2048, 4096.
+ If the property is not found, a default value of 512 will be used.
+
+- qcom,ratiometric:
+ Value type: <u32>
+ Definition: Channel calibration type. If this property is specified
+ VADC will use a special voltage references for channel
+ calibration. The available references are specified in the
+ as a u32 value setting (see below) and it is compulsory
+ to also specify this reference if ratiometric calibration
+ is selected.
+
+ If the property is not found, the channel will be
+ calibrated with the 0.625V and 1.25V reference channels, also
+ known as an absolute calibration.
+ The reference voltage pairs when using ratiometric calibration:
+ 0 = XO_IN/XOADC_GND
+ 1 = PMIC_IN/XOADC_GND
+ 2 = PMIC_IN/BMS_CSP
+ 3 (invalid)
+ 4 = XOADC_GND/XOADC_GND
+ 5 = XOADC_VREF/XOADC_GND
+
+Example:
+
+xoadc: xoadc at 197 {
+ compatible = "qcom,pm8058-adc";
+ reg = <0x197>;
+ interrupt-parent = <&pm8058>;
+ interrupts = <76 1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #io-channel-cells = <1>;
+
+ vcoin: adc-channel at 0 {
+ reg = <0x00>;
+ };
+ vbat: adc-channel at 1 {
+ reg = <0x01>;
+ };
+ dcin: adc-channel at 2 {
+ reg = <0x02>;
+ };
+ ichg: adc-channel at 3 {
+ reg = <0x03>;
+ };
+ vph_pwr: adc-channel at 4 {
+ reg = <0x04>;
+ };
+ usb_vbus: adc-channel at a {
+ reg = <0x0a>;
+ };
+ die_temp: adc-channel at b {
+ reg = <0x0b>;
+ };
+ ref_625mv: adc-channel at c {
+ reg = <0x0c>;
+ };
+ ref_1250mv: adc-channel at d {
+ reg = <0x0d>;
+ };
+ ref_325mv: adc-channel at e {
+ reg = <0x0e>;
+ };
+ ref_muxoff: adc-channel at f {
+ reg = <0x0f>;
+ };
+};
+
+
+/* IIO client node */
+iio-hwmon {
+ compatible = "iio-hwmon";
+ io-channels = <&xoadc 0x01>, /* Battery */
+ <&xoadc 0x02>, /* DC in (charger) */
+ <&xoadc 0x04>, /* VPH the main system voltage */
+ <&xoadc 0x0b>, /* Die temperature */
+ <&xoadc 0x0c>, /* Reference voltage 1.25V */
+ <&xoadc 0x0d>, /* Reference voltage 0.625V */
+ <&xoadc 0x0e>; /* Reference voltage 0.325V */
+};
--
2.9.3
^ permalink raw reply related
* [PATCH 2/3 v3] iio: adc: break out common code from SPMI VADC
From: Linus Walleij @ 2017-01-17 14:25 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117142515.10578-1-linus.walleij@linaro.org>
The SPMI VADC and the earlier XOADC share a subset of
common code, so to be able to use the same code in both
drivers, we break out a separate file with the common code,
prefix exported functions that are no longer static with
qcom_* and bake an object qcom-vadc.o that contains both
files: qcom-vadc-common.o and qcom-spmi-vadc.o.
Cc: linux-arm-kernel at lists.infradead.org
Cc: linux-arm-msm at vger.kernel.org
Cc: Ivan T. Ivanov <iivanov.xz@gmail.com>
Cc: Andy Gross <andy.gross@linaro.org>
Cc: Bjorn Andersson <bjorn.andersson@linaro.org>
Cc: Stephen Boyd <sboyd@codeaurora.org>
Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Cc: Rama Krishna Phani A <rphani@codeaurora.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
ChangeLog v2->v3:
- Rewrite on top of Rama Krishna's changes. Now we use the
generic channel properties, calibration graph and prescale
settings in all VADC drivers. I did away with the vtable
indirection which I just don't see the point of, it's
better to just pass the type of conversion function around
and have a switch case select the conversion. It was done like
that in the vendor tree but it's not a good idea.
ChangeLog v1->v2:
- No changes just reposting
---
drivers/iio/adc/Makefile | 3 +-
drivers/iio/adc/qcom-spmi-vadc.c | 325 ++-----------------------------------
drivers/iio/adc/qcom-vadc-common.c | 228 ++++++++++++++++++++++++++
drivers/iio/adc/qcom-vadc-common.h | 103 ++++++++++++
4 files changed, 348 insertions(+), 311 deletions(-)
create mode 100644 drivers/iio/adc/qcom-vadc-common.c
create mode 100644 drivers/iio/adc/qcom-vadc-common.h
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 260d083a2b3f..f8fe647b7abe 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -41,7 +41,8 @@ obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o
obj-$(CONFIG_NAU7802) += nau7802.o
obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
-obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
+qcom-vadc-y := qcom-vadc-common.o
+obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-vadc.o qcom-spmi-vadc.o
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
obj-$(CONFIG_STX104) += stx104.o
obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
diff --git a/drivers/iio/adc/qcom-spmi-vadc.c b/drivers/iio/adc/qcom-spmi-vadc.c
index 0a19761d656c..9e600bfd1765 100644
--- a/drivers/iio/adc/qcom-spmi-vadc.c
+++ b/drivers/iio/adc/qcom-spmi-vadc.c
@@ -28,6 +28,8 @@
#include <dt-bindings/iio/qcom,spmi-vadc.h>
+#include "qcom-vadc-common.h"
+
/* VADC register and bit definitions */
#define VADC_REVISION2 0x1
#define VADC_REVISION2_SUPPORTED_VADC 1
@@ -75,84 +77,10 @@
#define VADC_DATA 0x60 /* 16 bits */
-#define VADC_CONV_TIME_MIN_US 2000
-#define VADC_CONV_TIME_MAX_US 2100
-
-/* Min ADC code represents 0V */
-#define VADC_MIN_ADC_CODE 0x6000
-/* Max ADC code represents full-scale range of 1.8V */
-#define VADC_MAX_ADC_CODE 0xa800
-
-#define VADC_ABSOLUTE_RANGE_UV 625000
-#define VADC_RATIOMETRIC_RANGE 1800
-
-#define VADC_DEF_PRESCALING 0 /* 1:1 */
-#define VADC_DEF_DECIMATION 0 /* 512 */
-#define VADC_DEF_HW_SETTLE_TIME 0 /* 0 us */
-#define VADC_DEF_AVG_SAMPLES 0 /* 1 sample */
-#define VADC_DEF_CALIB_TYPE VADC_CALIB_ABSOLUTE
-
-#define VADC_DECIMATION_MIN 512
-#define VADC_DECIMATION_MAX 4096
-
-#define VADC_HW_SETTLE_DELAY_MAX 10000
-#define VADC_AVG_SAMPLES_MAX 512
-
-#define KELVINMIL_CELSIUSMIL 273150
-
-#define PMI_CHG_SCALE_1 -138890
-#define PMI_CHG_SCALE_2 391750000000LL
-
#define VADC_CHAN_MIN VADC_USBIN
#define VADC_CHAN_MAX VADC_LR_MUX3_BUF_PU1_PU2_XO_THERM
/**
- * struct vadc_map_pt - Map the graph representation for ADC channel
- * @x: Represent the ADC digitized code.
- * @y: Represent the physical data which can be temperature, voltage,
- * resistance.
- */
-struct vadc_map_pt {
- s32 x;
- s32 y;
-};
-
-/*
- * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels.
- * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for
- * calibration.
- */
-enum vadc_calibration {
- VADC_CALIB_ABSOLUTE = 0,
- VADC_CALIB_RATIOMETRIC
-};
-
-/**
- * struct vadc_linear_graph - Represent ADC characteristics.
- * @dy: numerator slope to calculate the gain.
- * @dx: denominator slope to calculate the gain.
- * @gnd: A/D word of the ground reference used for the channel.
- *
- * Each ADC device has different offset and gain parameters which are
- * computed to calibrate the device.
- */
-struct vadc_linear_graph {
- s32 dy;
- s32 dx;
- s32 gnd;
-};
-
-/**
- * struct vadc_prescale_ratio - Represent scaling ratio for ADC input.
- * @num: the inverse numerator of the gain applied to the input channel.
- * @den: the inverse denominator of the gain applied to the input channel.
- */
-struct vadc_prescale_ratio {
- u32 num;
- u32 den;
-};
-
-/**
* struct vadc_channel_prop - VADC channel property.
* @channel: channel number, refer to the channel list.
* @calibration: calibration type.
@@ -162,9 +90,8 @@ struct vadc_prescale_ratio {
* start of conversion.
* @avg_samples: ability to provide single result from the ADC
* that is an average of multiple measurements.
- * @scale_fn: Represents the scaling function to convert voltage
+ * @scale_fn_type: Represents the scaling function to convert voltage
* physical units desired by the client for the channel.
- * Referenced from enum vadc_scale_fn_type.
*/
struct vadc_channel_prop {
unsigned int channel;
@@ -173,7 +100,7 @@ struct vadc_channel_prop {
unsigned int prescale;
unsigned int hw_settle_time;
unsigned int avg_samples;
- unsigned int scale_fn;
+ enum vadc_scale_fn_type scale_fn_type;
};
/**
@@ -204,35 +131,6 @@ struct vadc_priv {
struct mutex lock;
};
-/**
- * struct vadc_scale_fn - Scaling function prototype
- * @scale: Function pointer to one of the scaling functions
- * which takes the adc properties, channel properties,
- * and returns the physical result.
- */
-struct vadc_scale_fn {
- int (*scale)(struct vadc_priv *, const struct vadc_channel_prop *,
- u16, int *);
-};
-
-/**
- * enum vadc_scale_fn_type - Scaling function to convert ADC code to
- * physical scaled units for the channel.
- * SCALE_DEFAULT: Default scaling to convert raw adc code to voltage (uV).
- * SCALE_THERM_100K_PULLUP: Returns temperature in millidegC.
- * Uses a mapping table with 100K pullup.
- * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade.
- * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC.
- * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp
- */
-enum vadc_scale_fn_type {
- SCALE_DEFAULT = 0,
- SCALE_THERM_100K_PULLUP,
- SCALE_PMIC_THERM,
- SCALE_XOTHERM,
- SCALE_PMI_CHG_TEMP,
-};
-
static const struct vadc_prescale_ratio vadc_prescale_ratios[] = {
{.num = 1, .den = 1},
{.num = 1, .den = 3},
@@ -244,44 +142,6 @@ static const struct vadc_prescale_ratio vadc_prescale_ratios[] = {
{.num = 1, .den = 10}
};
-/* Voltage to temperature */
-static const struct vadc_map_pt adcmap_100k_104ef_104fb[] = {
- {1758, -40},
- {1742, -35},
- {1719, -30},
- {1691, -25},
- {1654, -20},
- {1608, -15},
- {1551, -10},
- {1483, -5},
- {1404, 0},
- {1315, 5},
- {1218, 10},
- {1114, 15},
- {1007, 20},
- {900, 25},
- {795, 30},
- {696, 35},
- {605, 40},
- {522, 45},
- {448, 50},
- {383, 55},
- {327, 60},
- {278, 65},
- {237, 70},
- {202, 75},
- {172, 80},
- {146, 85},
- {125, 90},
- {107, 95},
- {92, 100},
- {79, 105},
- {68, 110},
- {59, 115},
- {51, 120},
- {44, 125}
-};
-
static int vadc_read(struct vadc_priv *vadc, u16 offset, u8 *data)
{
return regmap_bulk_read(vadc->regmap, vadc->base + offset, data, 1);
@@ -553,159 +413,6 @@ static int vadc_measure_ref_points(struct vadc_priv *vadc)
return ret;
}
-static int vadc_map_voltage_temp(const struct vadc_map_pt *pts,
- u32 tablesize, s32 input, s64 *output)
-{
- bool descending = 1;
- u32 i = 0;
-
- if (!pts)
- return -EINVAL;
-
- /* Check if table is descending or ascending */
- if (tablesize > 1) {
- if (pts[0].x < pts[1].x)
- descending = 0;
- }
-
- while (i < tablesize) {
- if ((descending) && (pts[i].x < input)) {
- /* table entry is less than measured*/
- /* value and table is descending, stop */
- break;
- } else if ((!descending) &&
- (pts[i].x > input)) {
- /* table entry is greater than measured*/
- /*value and table is ascending, stop */
- break;
- }
- i++;
- }
-
- if (i == 0) {
- *output = pts[0].y;
- } else if (i == tablesize) {
- *output = pts[tablesize - 1].y;
- } else {
- /* result is between search_index and search_index-1 */
- /* interpolate linearly */
- *output = (((s32)((pts[i].y - pts[i - 1].y) *
- (input - pts[i - 1].x)) /
- (pts[i].x - pts[i - 1].x)) +
- pts[i - 1].y);
- }
-
- return 0;
-}
-
-static void vadc_scale_calib(struct vadc_priv *vadc, u16 adc_code,
- const struct vadc_channel_prop *prop,
- s64 *scale_voltage)
-{
- *scale_voltage = (adc_code -
- vadc->graph[prop->calibration].gnd);
- *scale_voltage *= vadc->graph[prop->calibration].dx;
- *scale_voltage = div64_s64(*scale_voltage,
- vadc->graph[prop->calibration].dy);
- if (prop->calibration == VADC_CALIB_ABSOLUTE)
- *scale_voltage +=
- vadc->graph[prop->calibration].dx;
-
- if (*scale_voltage < 0)
- *scale_voltage = 0;
-}
-
-static int vadc_scale_volt(struct vadc_priv *vadc,
- const struct vadc_channel_prop *prop, u16 adc_code,
- int *result_uv)
-{
- const struct vadc_prescale_ratio *prescale;
- s64 voltage = 0, result = 0;
-
- vadc_scale_calib(vadc, adc_code, prop, &voltage);
-
- prescale = &vadc_prescale_ratios[prop->prescale];
- voltage = voltage * prescale->den;
- result = div64_s64(voltage, prescale->num);
- *result_uv = result;
-
- return 0;
-}
-
-static int vadc_scale_therm(struct vadc_priv *vadc,
- const struct vadc_channel_prop *prop, u16 adc_code,
- int *result_mdec)
-{
- s64 voltage = 0, result = 0;
-
- vadc_scale_calib(vadc, adc_code, prop, &voltage);
-
- if (prop->calibration == VADC_CALIB_ABSOLUTE)
- voltage = div64_s64(voltage, 1000);
-
- vadc_map_voltage_temp(adcmap_100k_104ef_104fb,
- ARRAY_SIZE(adcmap_100k_104ef_104fb),
- voltage, &result);
- result *= 1000;
- *result_mdec = result;
-
- return 0;
-}
-
-static int vadc_scale_die_temp(struct vadc_priv *vadc,
- const struct vadc_channel_prop *prop,
- u16 adc_code, int *result_mdec)
-{
- const struct vadc_prescale_ratio *prescale;
- s64 voltage = 0;
- u64 temp; /* Temporary variable for do_div */
-
- vadc_scale_calib(vadc, adc_code, prop, &voltage);
-
- if (voltage > 0) {
- prescale = &vadc_prescale_ratios[prop->prescale];
- temp = voltage * prescale->den;
- do_div(temp, prescale->num * 2);
- voltage = temp;
- } else {
- voltage = 0;
- }
-
- voltage -= KELVINMIL_CELSIUSMIL;
- *result_mdec = voltage;
-
- return 0;
-}
-
-static int vadc_scale_chg_temp(struct vadc_priv *vadc,
- const struct vadc_channel_prop *prop,
- u16 adc_code, int *result_mdec)
-{
- const struct vadc_prescale_ratio *prescale;
- s64 voltage = 0, result = 0;
-
- vadc_scale_calib(vadc, adc_code, prop, &voltage);
-
- prescale = &vadc_prescale_ratios[prop->prescale];
- voltage = voltage * prescale->den;
- voltage = div64_s64(voltage, prescale->num);
- voltage = ((PMI_CHG_SCALE_1) * (voltage * 2));
- voltage = (voltage + PMI_CHG_SCALE_2);
- result = div64_s64(voltage, 1000000);
- *result_mdec = result;
-
- return 0;
-}
-
-static int vadc_decimation_from_dt(u32 value)
-{
- if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
- value > VADC_DECIMATION_MAX)
- return -EINVAL;
-
- return __ffs64(value / VADC_DECIMATION_MIN);
-}
-
static int vadc_prescaling_from_dt(u32 num, u32 den)
{
unsigned int pre;
@@ -742,14 +449,6 @@ static int vadc_avg_samples_from_dt(u32 value)
return __ffs64(value);
}
-static struct vadc_scale_fn scale_fn[] = {
- [SCALE_DEFAULT] = {vadc_scale_volt},
- [SCALE_THERM_100K_PULLUP] = {vadc_scale_therm},
- [SCALE_PMIC_THERM] = {vadc_scale_die_temp},
- [SCALE_XOTHERM] = {vadc_scale_therm},
- [SCALE_PMI_CHG_TEMP] = {vadc_scale_chg_temp},
-};
-
static int vadc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val, int *val2,
long mask)
@@ -766,7 +465,13 @@ static int vadc_read_raw(struct iio_dev *indio_dev,
if (ret)
break;
- scale_fn[prop->scale_fn].scale(vadc, prop, adc_code, val);
+ ret = qcom_vadc_scale(prop->scale_fn_type,
+ &vadc->graph[prop->calibration],
+ &vadc_prescale_ratios[prop->prescale],
+ (prop->calibration == VADC_CALIB_ABSOLUTE),
+ adc_code, val);
+ if (ret)
+ break;
return IIO_VAL_INT;
case IIO_CHAN_INFO_RAW:
@@ -809,7 +514,7 @@ struct vadc_channels {
unsigned int prescale_index;
enum iio_chan_type type;
long info_mask;
- unsigned int scale_fn;
+ enum vadc_scale_fn_type scale_fn_type;
};
#define VADC_CHAN(_dname, _type, _mask, _pre, _scale) \
@@ -818,7 +523,7 @@ struct vadc_channels {
.prescale_index = _pre, \
.type = _type, \
.info_mask = _mask, \
- .scale_fn = _scale \
+ .scale_fn_type = _scale \
}, \
#define VADC_NO_CHAN(_dname, _type, _mask, _pre) \
@@ -976,7 +681,7 @@ static int vadc_get_dt_channel_data(struct device *dev,
ret = of_property_read_u32(node, "qcom,decimation", &value);
if (!ret) {
- ret = vadc_decimation_from_dt(value);
+ ret = qcom_vadc_decimation_from_dt(value);
if (ret < 0) {
dev_err(dev, "%02x invalid decimation %d\n",
chan, value);
@@ -1068,7 +773,7 @@ static int vadc_get_dt_data(struct vadc_priv *vadc, struct device_node *node)
return ret;
}
- prop.scale_fn = vadc_chans[prop.channel].scale_fn;
+ prop.scale_fn_type = vadc_chans[prop.channel].scale_fn_type;
vadc->chan_props[index] = prop;
vadc_chan = &vadc_chans[prop.channel];
diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
new file mode 100644
index 000000000000..39f8152f06f1
--- /dev/null
+++ b/drivers/iio/adc/qcom-vadc-common.c
@@ -0,0 +1,228 @@
+#include <linux/bug.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/math64.h>
+#include <linux/log2.h>
+#include <linux/err.h>
+
+#include "qcom-vadc-common.h"
+
+/* Voltage to temperature */
+static const struct vadc_map_pt adcmap_100k_104ef_104fb[] = {
+ {1758, -40},
+ {1742, -35},
+ {1719, -30},
+ {1691, -25},
+ {1654, -20},
+ {1608, -15},
+ {1551, -10},
+ {1483, -5},
+ {1404, 0},
+ {1315, 5},
+ {1218, 10},
+ {1114, 15},
+ {1007, 20},
+ {900, 25},
+ {795, 30},
+ {696, 35},
+ {605, 40},
+ {522, 45},
+ {448, 50},
+ {383, 55},
+ {327, 60},
+ {278, 65},
+ {237, 70},
+ {202, 75},
+ {172, 80},
+ {146, 85},
+ {125, 90},
+ {107, 95},
+ {92, 100},
+ {79, 105},
+ {68, 110},
+ {59, 115},
+ {51, 120},
+ {44, 125}
+};
+
+static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
+ u32 tablesize, s32 input, s64 *output)
+{
+ bool descending = 1;
+ u32 i = 0;
+
+ if (!pts)
+ return -EINVAL;
+
+ /* Check if table is descending or ascending */
+ if (tablesize > 1) {
+ if (pts[0].x < pts[1].x)
+ descending = 0;
+ }
+
+ while (i < tablesize) {
+ if ((descending) && (pts[i].x < input)) {
+ /* table entry is less than measured*/
+ /* value and table is descending, stop */
+ break;
+ } else if ((!descending) &&
+ (pts[i].x > input)) {
+ /* table entry is greater than measured*/
+ /*value and table is ascending, stop */
+ break;
+ }
+ i++;
+ }
+
+ if (i == 0) {
+ *output = pts[0].y;
+ } else if (i == tablesize) {
+ *output = pts[tablesize - 1].y;
+ } else {
+ /* result is between search_index and search_index-1 */
+ /* interpolate linearly */
+ *output = (((s32)((pts[i].y - pts[i - 1].y) *
+ (input - pts[i - 1].x)) /
+ (pts[i].x - pts[i - 1].x)) +
+ pts[i - 1].y);
+ }
+
+ return 0;
+}
+
+static void qcom_vadc_scale_calib(const struct vadc_linear_graph *calib_graph,
+ u16 adc_code,
+ bool absolute,
+ s64 *scale_voltage)
+{
+ *scale_voltage = (adc_code - calib_graph->gnd);
+ *scale_voltage *= calib_graph->dx;
+ *scale_voltage = div64_s64(*scale_voltage, calib_graph->dy);
+ if (absolute)
+ *scale_voltage += calib_graph->dx;
+
+ if (*scale_voltage < 0)
+ *scale_voltage = 0;
+}
+
+int qcom_vadc_scale_volt(const struct vadc_linear_graph *calib_graph,
+ const struct vadc_prescale_ratio *prescale,
+ bool absolute, u16 adc_code,
+ int *result_uv)
+{
+ s64 voltage = 0, result = 0;
+
+ qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
+
+ voltage = voltage * prescale->den;
+ result = div64_s64(voltage, prescale->num);
+ *result_uv = result;
+
+ return 0;
+}
+
+int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph,
+ const struct vadc_prescale_ratio *prescale,
+ bool absolute, u16 adc_code,
+ int *result_mdec)
+{
+ s64 voltage = 0, result = 0;
+ int ret;
+
+ qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
+
+ if (absolute)
+ voltage = div64_s64(voltage, 1000);
+
+ ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb,
+ ARRAY_SIZE(adcmap_100k_104ef_104fb),
+ voltage, &result);
+ if (ret)
+ return ret;
+
+ result *= 1000;
+ *result_mdec = result;
+
+ return 0;
+}
+
+int qcom_vadc_scale_die_temp(const struct vadc_linear_graph *calib_graph,
+ const struct vadc_prescale_ratio *prescale,
+ bool absolute,
+ u16 adc_code, int *result_mdec)
+{
+ s64 voltage = 0;
+ u64 temp; /* Temporary variable for do_div */
+
+ qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
+
+ if (voltage > 0) {
+ temp = voltage * prescale->den;
+ do_div(temp, prescale->num * 2);
+ voltage = temp;
+ } else {
+ voltage = 0;
+ }
+
+ voltage -= KELVINMIL_CELSIUSMIL;
+ *result_mdec = voltage;
+
+ return 0;
+}
+
+int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph,
+ const struct vadc_prescale_ratio *prescale,
+ bool absolute,
+ u16 adc_code, int *result_mdec)
+{
+ s64 voltage = 0, result = 0;
+
+ qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
+
+ voltage = voltage * prescale->den;
+ voltage = div64_s64(voltage, prescale->num);
+ voltage = ((PMI_CHG_SCALE_1) * (voltage * 2));
+ voltage = (voltage + PMI_CHG_SCALE_2);
+ result = div64_s64(voltage, 1000000);
+ *result_mdec = result;
+
+ return 0;
+}
+
+int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
+ const struct vadc_linear_graph *calib_graph,
+ const struct vadc_prescale_ratio *prescale,
+ bool absolute,
+ u16 adc_code, int *result)
+{
+ switch (scaletype) {
+ case SCALE_DEFAULT:
+ return qcom_vadc_scale_volt(calib_graph, prescale,
+ absolute, adc_code,
+ result);
+ case SCALE_THERM_100K_PULLUP:
+ case SCALE_XOTHERM:
+ return qcom_vadc_scale_therm(calib_graph, prescale,
+ absolute, adc_code,
+ result);
+ case SCALE_PMIC_THERM:
+ return qcom_vadc_scale_die_temp(calib_graph, prescale,
+ absolute, adc_code,
+ result);
+ case SCALE_PMI_CHG_TEMP:
+ return qcom_vadc_scale_chg_temp(calib_graph, prescale,
+ absolute, adc_code,
+ result);
+ default:
+ return -EINVAL;
+ }
+}
+
+int qcom_vadc_decimation_from_dt(u32 value)
+{
+ if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
+ value > VADC_DECIMATION_MAX)
+ return -EINVAL;
+
+ return __ffs64(value / VADC_DECIMATION_MIN);
+}
diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
new file mode 100644
index 000000000000..aacf051f3c2a
--- /dev/null
+++ b/drivers/iio/adc/qcom-vadc-common.h
@@ -0,0 +1,103 @@
+/*
+ * Code shared between the different Qualcomm PMIC voltage ADCs
+ */
+
+#define VADC_CONV_TIME_MIN_US 2000
+#define VADC_CONV_TIME_MAX_US 2100
+
+/* Min ADC code represents 0V */
+#define VADC_MIN_ADC_CODE 0x6000
+/* Max ADC code represents full-scale range of 1.8V */
+#define VADC_MAX_ADC_CODE 0xa800
+
+#define VADC_ABSOLUTE_RANGE_UV 625000
+#define VADC_RATIOMETRIC_RANGE 1800
+
+#define VADC_DEF_PRESCALING 0 /* 1:1 */
+#define VADC_DEF_DECIMATION 0 /* 512 */
+#define VADC_DEF_HW_SETTLE_TIME 0 /* 0 us */
+#define VADC_DEF_AVG_SAMPLES 0 /* 1 sample */
+#define VADC_DEF_CALIB_TYPE VADC_CALIB_ABSOLUTE
+
+#define VADC_DECIMATION_MIN 512
+#define VADC_DECIMATION_MAX 4096
+
+#define VADC_HW_SETTLE_DELAY_MAX 10000
+#define VADC_AVG_SAMPLES_MAX 512
+
+#define KELVINMIL_CELSIUSMIL 273150
+
+#define PMI_CHG_SCALE_1 -138890
+#define PMI_CHG_SCALE_2 391750000000LL
+
+/**
+ * struct vadc_map_pt - Map the graph representation for ADC channel
+ * @x: Represent the ADC digitized code.
+ * @y: Represent the physical data which can be temperature, voltage,
+ * resistance.
+ */
+struct vadc_map_pt {
+ s32 x;
+ s32 y;
+};
+
+/*
+ * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels.
+ * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for
+ * calibration.
+ */
+enum vadc_calibration {
+ VADC_CALIB_ABSOLUTE = 0,
+ VADC_CALIB_RATIOMETRIC
+};
+
+/**
+ * struct vadc_linear_graph - Represent ADC characteristics.
+ * @dy: numerator slope to calculate the gain.
+ * @dx: denominator slope to calculate the gain.
+ * @gnd: A/D word of the ground reference used for the channel.
+ *
+ * Each ADC device has different offset and gain parameters which are
+ * computed to calibrate the device.
+ */
+struct vadc_linear_graph {
+ s32 dy;
+ s32 dx;
+ s32 gnd;
+};
+
+/**
+ * struct vadc_prescale_ratio - Represent scaling ratio for ADC input.
+ * @num: the inverse numerator of the gain applied to the input channel.
+ * @den: the inverse denominator of the gain applied to the input channel.
+ */
+struct vadc_prescale_ratio {
+ u32 num;
+ u32 den;
+};
+
+/**
+ * enum vadc_scale_fn_type - Scaling function to convert ADC code to
+ * physical scaled units for the channel.
+ * SCALE_DEFAULT: Default scaling to convert raw adc code to voltage (uV).
+ * SCALE_THERM_100K_PULLUP: Returns temperature in millidegC.
+ * Uses a mapping table with 100K pullup.
+ * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade.
+ * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC.
+ * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp
+ */
+enum vadc_scale_fn_type {
+ SCALE_DEFAULT = 0,
+ SCALE_THERM_100K_PULLUP,
+ SCALE_PMIC_THERM,
+ SCALE_XOTHERM,
+ SCALE_PMI_CHG_TEMP,
+};
+
+int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
+ const struct vadc_linear_graph *calib_graph,
+ const struct vadc_prescale_ratio *prescale,
+ bool absolute,
+ u16 adc_code, int *result_mdec);
+
+int qcom_vadc_decimation_from_dt(u32 value);
--
2.9.3
^ permalink raw reply related
* [PATCH 3/3 v3] iio: adc: add a driver for Qualcomm PM8xxx HK/XOADC
From: Linus Walleij @ 2017-01-17 14:25 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170117142515.10578-1-linus.walleij@linaro.org>
The Qualcomm PM8xxx PMICs contain a simpler ADC than its
successors (already in the kernel as qcom-spmi-adc.c):
the HK/XO ADC (Housekeeping/Chrystal oscillator ADC).
As far as I can understand this is equal to the PMICs
using SSBI transport and encompass PM8018, PM8038,
PM8058, and PM8921, so this is shortly named PM8xxx.
This ADC monitors a bunch of on-board voltages and the die
temperature of the PMIC itself, but it can also be routed
to convert a few external MPPs (multi-purpose pins). On
the APQ8060 DragonBoard this feature is used to let this
ADC convert an analog ALS (Ambient Light Sensor) voltage
signal from a Capella CM3605 ALS into a LUX value.
Developed and tested with APQ8060 DragonBoard based on
Ivan's driver and Rama Krishna's patches. The SPMI VADC
driver is quite different, but share enough minor
functionality that I have split out to the common file
in a previous patch.
Cc: linux-arm-kernel at lists.infradead.org
Cc: linux-arm-msm at vger.kernel.org
Cc: Ivan T. Ivanov <iivanov.xz@gmail.com>
Cc: Andy Gross <andy.gross@linaro.org>
Cc: Bjorn Andersson <bjorn.andersson@linaro.org>
Cc: Stephen Boyd <sboyd@codeaurora.org>
Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Cc: Rama Krishna Phani A <rphani@codeaurora.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
ChangeLog v2->v3:
- Rebase using Rama Krishna's improvements, set up defaults for
scaling function and prescaler for each channel, per ASIC variant.
- Also set up RSV per-variant as the vendor tree does this and
it seems necessary to set RSV0 on the muxoff channel to get
readings from the XO thermistor.
- Encode default IIO type in the default per-PMIC channel table.
- Fixed a bug in ratiometric calibration (needed to force ratiometric
mode).
- Dropped PM8917: that doesn't have any XOADC.
- Added a special flag for the PM8058: it apparently needs special
RSV muxing due to being necessarily different.
- Augmented to handle altered ratiometric DT property.
- Do not hardcode that the reference voltage must be 2.2V, just fetch
and enable the regulator. Different PMICs use different reference
voltages.
ChangeLog v1->v2:
- Introduce a mutex to avoid different clients stepping on each others'
toes
- Add an of_xlate function to be sure to match the right ADC
- Add all compatible strings, not just PM8058
---
drivers/iio/adc/Kconfig | 10 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/qcom-pm8xxx-xoadc.c | 922 ++++++++++++++++++++++++++++++++++++
3 files changed, 933 insertions(+)
create mode 100644 drivers/iio/adc/qcom-pm8xxx-xoadc.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 817693a14eb9..3184e36b1987 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -421,6 +421,16 @@ config PALMAS_GPADC
is used in smartphones and tablets and supports a 16 channel
general purpose ADC.
+config QCOM_PM8XXX_XOADC
+ tristate "Qualcomm SSBI PM8xxx PMIC XOADCs"
+ depends on MFD_PM8XXX
+ help
+ ADC driver for the XOADC portions of the Qualcomm PM8xxx PMICs
+ using SSBI transport: PM8018, PM8038, PM8058, PM8921.
+
+ To compile this driver as a module, choose M here: the module
+ will be called qcom-pm8xxx-xoadc.
+
config QCOM_SPMI_IADC
tristate "Qualcomm SPMI PMIC current ADC"
depends on SPMI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index f8fe647b7abe..8d2b80cdcb6f 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
qcom-vadc-y := qcom-vadc-common.o
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-vadc.o qcom-spmi-vadc.o
+obj-$(CONFIG_QCOM_PM8XXX_XOADC) += qcom-vadc.o qcom-pm8xxx-xoadc.o
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
obj-$(CONFIG_STX104) += stx104.o
obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
diff --git a/drivers/iio/adc/qcom-pm8xxx-xoadc.c b/drivers/iio/adc/qcom-pm8xxx-xoadc.c
new file mode 100644
index 000000000000..8b2119694e8c
--- /dev/null
+++ b/drivers/iio/adc/qcom-pm8xxx-xoadc.c
@@ -0,0 +1,922 @@
+/*
+ * Qualcomm PM8xxx PMIC XOADC driver
+ *
+ * These ADCs are known as HK/XO (house keeping / chrystal oscillator)
+ * "XO" in "XOADC" means Chrystal Oscillator. It's a bunch of
+ * specific-purpose and general purpose ADC converters and channels.
+ *
+ * Copyright (C) 2016 Linaro Ltd.
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ */
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/regulator/consumer.h>
+
+#include "qcom-vadc-common.h"
+
+/*
+ * Definitions for the "user processor" registers lifted from the v3.4
+ * Qualcomm tree. Their kernel has two out-of-tree drivers for the ADC:
+ * drivers/misc/pmic8058-xoadc.c
+ * drivers/hwmon/pm8xxx-adc.c
+ * None of them contain any complete register specification, so this is
+ * a best effort of combining the information.
+ */
+
+/* These appear to be "battery monitor" registers */
+#define ADC_ARB_BTM_CNTRL1 0x17e
+#define ADC_ARB_BTM_CNTRL1_EN_BTM BIT(0)
+#define ADC_ARB_BTM_CNTRL1_SEL_OP_MODE BIT(1)
+#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL1 BIT(2)
+#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL2 BIT(3)
+#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL3 BIT(4)
+#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL4 BIT(5)
+#define ADC_ARB_BTM_CNTRL1_EOC BIT(6)
+#define ADC_ARB_BTM_CNTRL1_REQ BIT(7)
+
+#define ADC_ARB_BTM_AMUX_CNTRL 0x17f
+#define ADC_ARB_BTM_ANA_PARAM 0x180
+#define ADC_ARB_BTM_DIG_PARAM 0x181
+#define ADC_ARB_BTM_RSV 0x182
+#define ADC_ARB_BTM_DATA1 0x183
+#define ADC_ARB_BTM_DATA0 0x184
+#define ADC_ARB_BTM_BAT_COOL_THR1 0x185
+#define ADC_ARB_BTM_BAT_COOL_THR0 0x186
+#define ADC_ARB_BTM_BAT_WARM_THR1 0x187
+#define ADC_ARB_BTM_BAT_WARM_THR0 0x188
+#define ADC_ARB_BTM_CNTRL2 0x18c
+
+/* Proper ADC registers */
+
+#define ADC_ARB_USRP_CNTRL 0x197
+#define ADC_ARB_USRP_CNTRL_EN_ARB BIT(0)
+#define ADC_ARB_USRP_CNTRL_RSV1 BIT(1)
+#define ADC_ARB_USRP_CNTRL_RSV2 BIT(2)
+#define ADC_ARB_USRP_CNTRL_RSV3 BIT(3)
+#define ADC_ARB_USRP_CNTRL_RSV4 BIT(4)
+#define ADC_ARB_USRP_CNTRL_RSV5 BIT(5)
+#define ADC_ARB_USRP_CNTRL_EOC BIT(6)
+#define ADC_ARB_USRP_CNTRL_REQ BIT(7)
+
+#define ADC_ARB_USRP_AMUX_CNTRL 0x198
+#define ADC_ARB_USRP_AMUX_CNTRL_RSV0 BIT(0)
+#define ADC_ARB_USRP_AMUX_CNTRL_RSV1 BIT(1)
+#define ADC_ARB_USRP_AMUX_CNTRL_PREMUX0 BIT(2)
+#define ADC_ARB_USRP_AMUX_CNTRL_PREMUX1 BIT(3)
+#define ADC_ARB_USRP_AMUX_CNTRL_SEL0 BIT(4)
+#define ADC_ARB_USRP_AMUX_CNTRL_SEL1 BIT(5)
+#define ADC_ARB_USRP_AMUX_CNTRL_SEL2 BIT(6)
+#define ADC_ARB_USRP_AMUX_CNTRL_SEL3 BIT(7)
+#define ADC_AMUX_PREMUX_SHIFT 2
+#define ADC_AMUX_SEL_SHIFT 4
+
+/* We know very little about the bits in this register */
+#define ADC_ARB_USRP_ANA_PARAM 0x199
+#define ADC_ARB_USRP_ANA_PARAM_DIS 0xFE
+#define ADC_ARB_USRP_ANA_PARAM_EN 0xFF
+
+#define ADC_ARB_USRP_DIG_PARAM 0x19A
+#define ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 BIT(0)
+#define ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 BIT(1)
+#define ADC_ARB_USRP_DIG_PARAM_CLK_RATE0 BIT(2)
+#define ADC_ARB_USRP_DIG_PARAM_CLK_RATE1 BIT(3)
+#define ADC_ARB_USRP_DIG_PARAM_EOC BIT(4)
+/*
+ * On a later ADC the decimation factors are defined as
+ * 00 = 512, 01 = 1024, 10 = 2048, 11 = 4096 so assume this
+ * holds also for this older XOADC.
+ */
+#define ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 BIT(5)
+#define ADC_ARB_USRP_DIG_PARAM_DEC_RATE1 BIT(6)
+#define ADC_ARB_USRP_DIG_PARAM_EN BIT(7)
+#define ADC_DIG_PARAM_DEC_SHIFT 5
+
+#define ADC_ARB_USRP_RSV 0x19B
+#define ADC_ARB_USRP_RSV_RST BIT(0)
+#define ADC_ARB_USRP_RSV_DTEST0 BIT(1)
+#define ADC_ARB_USRP_RSV_DTEST1 BIT(2)
+#define ADC_ARB_USRP_RSV_OP BIT(3)
+#define ADC_ARB_USRP_RSV_IP_SEL0 BIT(4)
+#define ADC_ARB_USRP_RSV_IP_SEL1 BIT(5)
+#define ADC_ARB_USRP_RSV_IP_SEL2 BIT(6)
+#define ADC_ARB_USRP_RSV_TRM BIT(7)
+#define ADC_RSV_IP_SEL_SHIFT 4
+
+#define ADC_ARB_USRP_DATA0 0x19D
+#define ADC_ARB_USRP_DATA1 0x19C
+
+/**
+ * Physical channels, the vendor tree call these "channel path type"
+ * for some reason, they have pretty much hardwired characteristics.
+ * MPP = Multi-Purpose Pin. Channel info:
+ *
+ * @CHANNEL_VCOIN: Coincell backup power source
+ * @CHANNEL_VBAT: Battery voltage
+ * @CHANNEL_DCIN: Charger voltage
+ * @CHANNEL_ICHG: Charger current monitor
+ * @CHANNEL_VPH_PWR: Main system power VPH
+ * @CHANNEL_MPP5: general purpose, used for headset detection on some set-ups
+ * @CHANNEL_MPP6: general purpose, used for battery temperature on some set-ups
+ * @CHANNEL_MPP7: general purpose, used for system temperature on some set-ups
+ * @CHANNEL_MPP8: general purpose, used for battery ID detection on some set-ups
+ * @CHANNEL_MPP9: general purpose, used for external charger current on some set-ups
+ * @CHANNEL_USB_VBUS: USB bus voltage, used for USB charging
+ * @CHANNEL_DIE_TEMP: PMIC die temperature
+ * @CHANNEL_INTERNAL: 625mV reference channel
+ * @CHANNEL_125V: 1250mV reference channel
+ * @CHANNEL_INTERNAL_2: 325mV reference channel
+ * @CHANNEL_MUXOFF: channel to reduce input load on mux, apparentlt also
+ * measures XO temperature
+ */
+#define CHANNEL_VCOIN 0x0
+#define CHANNEL_VBAT 0x1
+#define CHANNEL_DCIN 0x2
+#define CHANNEL_ICHG 0x3
+#define CHANNEL_VPH_PWR 0x4
+#define CHANNEL_MPP5 0x5
+#define CHANNEL_MPP6 0x6
+#define CHANNEL_MPP7 0x7
+#define CHANNEL_MPP8 0x8
+#define CHANNEL_MPP9 0x9
+#define CHANNEL_USB_VBUS 0xa
+#define CHANNEL_DIE_TEMP 0xb
+#define CHANNEL_INTERNAL 0xc
+#define CHANNEL_125V 0xd
+#define CHANNEL_INTERNAL_2 0xe
+#define CHANNEL_MUXOFF 0xf
+
+#define XOADC_CHANNELS 16 /* 4 bits */
+
+/* MPP = Multi-Purpose Pins, premux scaling */
+#define MPP_SCALE_0 0x0 /* No scaling on the signal */
+#define MPP_SCALE_1 0x1 /* Unity scaling selected by the user */
+#define MPP_SCALE_1_DIV3 0x2 /* 1/3 prescaler on the input from MPP */
+
+/* Defines reference voltage for the XOADC */
+#define AMUX_RSV0 0x0 /* XO_IN/XOADC_GND, special selection to read XO temp */
+#define AMUX_RSV1 0x1 /* PMIC_IN/XOADC_GND */
+#define AMUX_RSV2 0x2 /* PMIC_IN/BMS_CSP */
+#define AMUX_RSV3 0x3 /* not used */
+#define AMUX_RSV4 0x4 /* XOADC_GND/XOADC_GND */
+#define AMUX_RSV5 0x5 /* XOADC_VREF/XOADC_GND */
+#define XOADC_RSV_MAX 5 /* 3 bits 0..7, 3 and 6,7 are invalid */
+
+/*
+ * The different hardware channel names, hardware characteristics
+ * and sensible default settings.
+ * @datasheet_name: the hardwarename of this channel
+ * @prescale: the channels have hard-coded prescale ratios defined
+ * by the hardware, this tells us what it is
+ * @type: corresponding IIO channel type, usually IIO_VOLTAGE or
+ * IIO_TEMP
+ * @scale_fn_type: the liner interpolation etc to convert the
+ * ADC code to the value that IIO expects, in uV or millicelsius
+ * etc. This scale function can be pretty elaborate if different
+ * thermistors are connected or other hardware characteristics are
+ * deployed.
+ * @amux_ip_rsv: ratiometric scale value used by the analig muxer: this
+ * selects the reference voltage for ratiometric scaling
+ */
+struct xoadc_channel {
+ const char *datasheet_name;
+ const struct vadc_prescale_ratio prescale;
+ enum iio_chan_type type;
+ enum vadc_scale_fn_type scale_fn_type;
+ u8 amux_ip_rsv:3;
+};
+
+#define XOADC_CHAN(_dname, _type, _prenum, _preden, _scale, _amip) \
+ [CHANNEL_##_dname] = { \
+ .datasheet_name = __stringify(_dname), \
+ .prescale = { .num = _prenum, .den = _preden }, \
+ .type = _type, \
+ .scale_fn_type = _scale, \
+ .amux_ip_rsv = _amip, \
+ } \
+
+/*
+ * Taken from arch/arm/mach-msm/board-9615.c in the vendor tree:
+ * TODO: incomplete, needs testing.
+ */
+static const struct xoadc_channel pm8018_xoadc_channels[] = {
+ XOADC_CHAN(VCOIN, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(VBAT, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(VPH_PWR, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(DIE_TEMP, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1),
+ /* Used for battery ID or battery temperature */
+ XOADC_CHAN(MPP8, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV2),
+ XOADC_CHAN(INTERNAL, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(125V, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MUXOFF, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0),
+};
+
+/*
+ * Taken from arch/arm/mach-msm/board-8930-pmic.c in the vendor tree:
+ * TODO: needs testing.
+ */
+static const struct xoadc_channel pm8038_xoadc_channels[] = {
+ XOADC_CHAN(VCOIN, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(VBAT, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(DCIN, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(ICHG, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(VPH_PWR, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP5, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP6, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP7, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ /* MPP8 used for battery temperature in most cases */
+ XOADC_CHAN(MPP8, IIO_TEMP, 1, 1, SCALE_THERM_100K_PULLUP, AMUX_RSV2),
+ XOADC_CHAN(MPP9, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(USB_VBUS, IIO_VOLTAGE, 1, 4, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(DIE_TEMP, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1),
+ XOADC_CHAN(INTERNAL, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(125V, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(INTERNAL_2, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MUXOFF, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0),
+};
+
+/*
+ * This was created by cross-referencing the vendor tree
+ * arch/arm/mach-msm/board-msm8x60.c msm_adc_channels_data[]
+ * with the "channel types" (first field) to find the right
+ * configuration for these channels on an MSM8x60 i.e. PM8058
+ * setup.
+ */
+static const struct xoadc_channel pm8058_xoadc_channels[] = {
+ XOADC_CHAN(VCOIN, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(VBAT, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(DCIN, IIO_VOLTAGE, 1, 10, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(ICHG, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(VPH_PWR, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP5, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP6, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP7, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP8, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP9, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(USB_VBUS, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(DIE_TEMP, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1),
+ XOADC_CHAN(INTERNAL, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(125V, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(INTERNAL_2, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MUXOFF, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0),
+};
+
+/*
+ * The PM8921 is similar, this comes from the vendor tree
+ * board-flo-pmic.c (Nexus 7) and board-8064-pmic.c
+ * Notably the prescalers are differing.
+ */
+static const struct xoadc_channel pm8921_xoadc_channels[] = {
+ XOADC_CHAN(VCOIN, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(VBAT, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(DCIN, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(ICHG, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(VPH_PWR, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1),
+ /* MPP scaling is unknown, these scalings are a guess */
+ XOADC_CHAN(MPP5, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP6, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MPP7, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ /* MPP8 is usually used for battery temperature */
+ XOADC_CHAN(MPP8, IIO_TEMP, 1, 1, SCALE_THERM_100K_PULLUP, AMUX_RSV1),
+ XOADC_CHAN(MPP9, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(USB_VBUS, IIO_VOLTAGE, 1, 4, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(DIE_TEMP, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1),
+ XOADC_CHAN(INTERNAL, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(125V, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(INTERNAL_2, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1),
+ XOADC_CHAN(MUXOFF, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0),
+};
+
+
+/**
+ * struct pm8xxx_chan_info - ADC channel information
+ * @name: name of this channel
+ * @calibration: whether to use absolute or ratiometric calibration
+ * @scale_fn_type: scaling function type
+ * @amux_channel: channel 0..15
+ * @decimation: 0,1,2,3
+ * @amux_mpp_channel: MPP channel 0..3, selects prescaling
+ * @amux_ip_rsv: ratiometric scale value if using ratiometric
+ * calibration: 0, 1, 2, 4, 5.
+ */
+struct pm8xxx_chan_info {
+ const char *name;
+ enum vadc_calibration calibration;
+ const struct vadc_prescale_ratio *prescale;
+ enum vadc_scale_fn_type scale_fn_type;
+ u8 amux_channel:4;
+ u8 decimation:2;
+ u8 amux_mpp_channel:2;
+ u8 amux_ip_rsv:3;
+};
+
+/**
+ * struct pm8xxx_xoadc - state container for the XOADC
+ * @dev: pointer to device
+ * @map: regmap to access registers
+ * @vref: reference voltage regulator
+ * @hw_channels: the hardware channel defaults, information about the hardcoded
+ * characteristics of the channels, and sensible default settings
+ * @nchans: number of channels, configured by the device tree
+ * @chans: the channel information per-channel, configured by the device tree
+ * @iio_chans: IIO channel specifiers
+ * @graph: linear calibration parameters for absolute and
+ * ratiometric measurements
+ * @complete: completion to indicate end of conversion
+ * @lock: lock to restrict access to the hardware to one client at the time
+ * @is_pm8058: the PM8058 has some bugs that need handling
+ */
+struct pm8xxx_xoadc {
+ struct device *dev;
+ struct regmap *map;
+ struct regulator *vref;
+ const struct xoadc_channel *hw_channels;
+ unsigned int nchans;
+ struct pm8xxx_chan_info *chans;
+ struct iio_chan_spec *iio_chans;
+ struct vadc_linear_graph graph[2];
+ struct completion complete;
+ struct mutex lock;
+ bool is_pm8058;
+};
+
+static irqreturn_t pm8xxx_eoc_irq(int irq, void *d)
+{
+ struct iio_dev *indio_dev = d;
+ struct pm8xxx_xoadc *adc = iio_priv(indio_dev);
+
+ complete(&adc->complete);
+
+ return IRQ_HANDLED;
+}
+
+static struct pm8xxx_chan_info *
+pm8xxx_get_channel(struct pm8xxx_xoadc *adc, u8 chan)
+{
+ struct pm8xxx_chan_info *ch;
+ int i;
+
+ for (i = 0; i < adc->nchans; i++) {
+ ch = &adc->chans[i];
+ if (ch->amux_channel == chan)
+ break;
+ }
+ if (i == adc->nchans)
+ return NULL;
+
+ return ch;
+}
+
+static int pm8xxx_read_channel_rsv(struct pm8xxx_xoadc *adc,
+ const struct pm8xxx_chan_info *ch,
+ u8 rsv, u16 *adc_code,
+ bool force_ratiometric)
+{
+ int ret;
+ unsigned int val;
+ u8 rsvmask, rsvval;
+ u8 lsb, msb;
+
+ dev_dbg(adc->dev, "read channel \"%s\", amux %d, mpp %d, rsv %d\n",
+ ch->name, ch->amux_channel, ch->amux_mpp_channel, rsv);
+
+ mutex_lock(&adc->lock);
+
+ /* Mux in this channel */
+ ret = regmap_write(adc->map, ADC_ARB_USRP_AMUX_CNTRL,
+ ch->amux_channel << ADC_AMUX_SEL_SHIFT |
+ ch->amux_mpp_channel << ADC_AMUX_PREMUX_SHIFT);
+ if (ret)
+ goto unlock;
+
+ /* Set up ratiometric scale value, mask off all bits except these */
+ rsvmask = (ADC_ARB_USRP_RSV_RST | ADC_ARB_USRP_RSV_DTEST0 |
+ ADC_ARB_USRP_RSV_DTEST1 | ADC_ARB_USRP_RSV_OP);
+ if (adc->is_pm8058 && !force_ratiometric) {
+ /*
+ * Apparently the PM8058 has some kind of bug which is
+ * reflected in the vendor tree drivers/misc/pmix8058-xoadc.c
+ * which just hardcodes the RSV selector to SEL1 (0x20) for
+ * most cases and SEL0 (0x10) for the MUXOFF channel only.
+ * If we force ratiometric (currently only done when attempting
+ * to do ratiometric calibration) this doesn't seem to work
+ * very well and I suspect ratiometric conversion is simply
+ * broken or not supported on the PM8058.
+ *
+ * Maybe IO_SEL2 doesn't exist on PM8058 and bits 4 & 5 select
+ * the mode alone.
+ *
+ * Some PM8058 register documentation would be nice to get
+ * this right.
+ */
+ if (ch->amux_channel == CHANNEL_MUXOFF)
+ rsvval = ADC_ARB_USRP_RSV_IP_SEL0;
+ else
+ rsvval = ADC_ARB_USRP_RSV_IP_SEL1;
+ } else {
+ if (rsv == 0xff)
+ rsvval = (ch->amux_ip_rsv << ADC_RSV_IP_SEL_SHIFT) |
+ ADC_ARB_USRP_RSV_TRM;
+ else
+ rsvval = (rsv << ADC_RSV_IP_SEL_SHIFT) |
+ ADC_ARB_USRP_RSV_TRM;
+ }
+
+ ret = regmap_update_bits(adc->map,
+ ADC_ARB_USRP_RSV,
+ ~rsvmask,
+ rsvval);
+ if (ret)
+ goto unlock;
+
+ ret = regmap_write(adc->map, ADC_ARB_USRP_ANA_PARAM,
+ ADC_ARB_USRP_ANA_PARAM_DIS);
+ if (ret)
+ goto unlock;
+
+ /* Decimation factor */
+ ret = regmap_write(adc->map, ADC_ARB_USRP_DIG_PARAM,
+ ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 |
+ ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 |
+ ch->decimation << ADC_DIG_PARAM_DEC_SHIFT);
+ if (ret)
+ goto unlock;
+
+ ret = regmap_write(adc->map, ADC_ARB_USRP_ANA_PARAM,
+ ADC_ARB_USRP_ANA_PARAM_EN);
+ if (ret)
+ goto unlock;
+
+ /* Enable the arbiter, the Qualcomm code does it twice like this */
+ ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL,
+ ADC_ARB_USRP_CNTRL_EN_ARB);
+ if (ret)
+ goto unlock;
+ ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL,
+ ADC_ARB_USRP_CNTRL_EN_ARB);
+ if (ret)
+ goto unlock;
+
+
+ /* Fire a request! */
+ reinit_completion(&adc->complete);
+ ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL,
+ ADC_ARB_USRP_CNTRL_EN_ARB |
+ ADC_ARB_USRP_CNTRL_REQ);
+ if (ret)
+ goto unlock;
+
+ /* Next the interrupt occurs */
+ ret = wait_for_completion_timeout(&adc->complete,
+ VADC_CONV_TIME_MAX_US);
+ if (!ret) {
+ dev_err(adc->dev, "conversion timed out\n");
+ ret = -ETIMEDOUT;
+ goto unlock;
+ }
+
+ ret = regmap_read(adc->map, ADC_ARB_USRP_DATA0, &val);
+ if (ret)
+ goto unlock;
+ lsb = val;
+ ret = regmap_read(adc->map, ADC_ARB_USRP_DATA1, &val);
+ if (ret)
+ goto unlock;
+ msb = val;
+ *adc_code = (msb << 8) | lsb;
+
+ /* Turn off the ADC by setting the arbiter to 0 twice */
+ ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, 0);
+ if (ret)
+ goto unlock;
+ ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, 0);
+ if (ret)
+ goto unlock;
+
+unlock:
+ mutex_unlock(&adc->lock);
+ return ret;
+}
+
+static int pm8xxx_read_channel(struct pm8xxx_xoadc *adc,
+ const struct pm8xxx_chan_info *ch,
+ u16 *adc_code)
+{
+ /*
+ * Normally we just use the ratiometric scale value (RSV) predefined
+ * for the channel, but during calibration we need to modify this
+ * so this wrapper is a helper hiding the more complex version.
+ */
+ return pm8xxx_read_channel_rsv(adc, ch, 0xff, adc_code, false);
+}
+
+static int pm8xxx_calibrate_device(struct pm8xxx_xoadc *adc)
+{
+ const struct pm8xxx_chan_info *ch;
+ u16 read_1250v;
+ u16 read_0625v;
+ u16 read_nomux_rsv5;
+ u16 read_nomux_rsv4;
+ int ret;
+
+
+ adc->graph[VADC_CALIB_ABSOLUTE].dx = VADC_ABSOLUTE_RANGE_UV;
+ adc->graph[VADC_CALIB_RATIOMETRIC].dx = VADC_RATIOMETRIC_RANGE;
+
+ /* Common reference channel calibration */
+ ch = pm8xxx_get_channel(adc, CHANNEL_125V);
+ if (!ch)
+ return -ENODEV;
+ ret = pm8xxx_read_channel(adc, ch, &read_1250v);
+ if (ret) {
+ dev_err(adc->dev, "could not read 1.25V reference channel\n");
+ return -ENODEV;
+ }
+ ch = pm8xxx_get_channel(adc, CHANNEL_INTERNAL);
+ if (!ch)
+ return -ENODEV;
+ ret = pm8xxx_read_channel(adc, ch, &read_0625v);
+ if (ret) {
+ dev_err(adc->dev, "could not read 0.625V reference channel\n");
+ return -ENODEV;
+ }
+ if (read_1250v == read_0625v) {
+ dev_err(adc->dev, "read same ADC code for 1.25V and 0.625V\n");
+ return -ENODEV;
+ }
+
+ adc->graph[VADC_CALIB_ABSOLUTE].dy = read_1250v - read_0625v;
+ adc->graph[VADC_CALIB_ABSOLUTE].gnd = read_0625v;
+
+ dev_info(adc->dev, "absolute calibration dx = %d uV, dy = %d units\n",
+ VADC_ABSOLUTE_RANGE_UV, adc->graph[VADC_CALIB_ABSOLUTE].dy);
+
+ /* Ratiometric calibration */
+ ch = pm8xxx_get_channel(adc, CHANNEL_MUXOFF);
+ if (!ch)
+ return -ENODEV;
+ ret = pm8xxx_read_channel_rsv(adc, ch, AMUX_RSV5,
+ &read_nomux_rsv5, true);
+ if (ret) {
+ dev_err(adc->dev, "could not read MUXOFF reference channel\n");
+ return -ENODEV;
+ }
+ ret = pm8xxx_read_channel_rsv(adc, ch, AMUX_RSV4,
+ &read_nomux_rsv4, true);
+ if (ret) {
+ dev_err(adc->dev, "could not read MUXOFF reference channel\n");
+ return -ENODEV;
+ }
+ adc->graph[VADC_CALIB_RATIOMETRIC].dy =
+ read_nomux_rsv5 - read_nomux_rsv4;
+ adc->graph[VADC_CALIB_RATIOMETRIC].gnd = read_nomux_rsv4;
+
+ dev_info(adc->dev, "ratiometric calibration dx = %d, dy = %d units\n",
+ VADC_RATIOMETRIC_RANGE,
+ adc->graph[VADC_CALIB_RATIOMETRIC].dy);
+
+ return 0;
+}
+
+static int pm8xxx_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct pm8xxx_xoadc *adc = iio_priv(indio_dev);
+ const struct pm8xxx_chan_info *ch;
+ u16 adc_code;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ ch = pm8xxx_get_channel(adc, chan->address);
+ if (!ch) {
+ dev_err(adc->dev, "no such channel %lu\n",
+ chan->address);
+ return -EINVAL;
+ }
+ ret = pm8xxx_read_channel(adc, ch, &adc_code);
+ if (ret)
+ return ret;
+
+ ret = qcom_vadc_scale(ch->scale_fn_type,
+ &adc->graph[ch->calibration],
+ ch->prescale,
+ (ch->calibration == VADC_CALIB_ABSOLUTE),
+ adc_code, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_RAW:
+ ch = pm8xxx_get_channel(adc, chan->address);
+ if (!ch) {
+ dev_err(adc->dev, "no such channel %lu\n",
+ chan->address);
+ return -EINVAL;
+ }
+ ret = pm8xxx_read_channel(adc, ch, &adc_code);
+ if (ret)
+ return ret;
+
+ *val = (int)adc_code;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int pm8xxx_of_xlate(struct iio_dev *indio_dev,
+ const struct of_phandle_args *iiospec)
+{
+ struct pm8xxx_xoadc *adc = iio_priv(indio_dev);
+ unsigned int i;
+
+ for (i = 0; i < adc->nchans; i++)
+ if (adc->iio_chans[i].channel == iiospec->args[0])
+ return i;
+
+ return -EINVAL;
+}
+
+static const struct iio_info pm8xxx_xoadc_info = {
+ .driver_module = THIS_MODULE,
+ .of_xlate = pm8xxx_of_xlate,
+ .read_raw = pm8xxx_read_raw,
+};
+
+static int pm8xxx_xoadc_parse_channel(struct device *dev,
+ struct device_node *np,
+ const struct xoadc_channel *hw_channels,
+ struct iio_chan_spec *iio_chan,
+ struct pm8xxx_chan_info *ch)
+{
+ const char *name = np->name;
+ u32 chan, rsv, dec;
+ int ret;
+
+ ret = of_property_read_u32(np, "reg", &chan);
+ if (ret) {
+ dev_err(dev, "invalid channel number %s\n", name);
+ return ret;
+ }
+ if (chan >= XOADC_CHANNELS) {
+ dev_err(dev, "%s too big channel number %d\n", name, chan);
+ return -EINVAL;
+ }
+
+ /* Look up default channel settings */
+ ch->amux_channel = chan;
+ ch->name = name;
+ ch->prescale = &hw_channels[chan].prescale;
+ ch->scale_fn_type = hw_channels[chan].scale_fn_type;
+ ch->amux_ip_rsv = hw_channels[chan].amux_ip_rsv;
+ /* Everyone seems to use absolute calibration except in special cases */
+ ch->calibration = VADC_CALIB_ABSOLUTE;
+ /* Everyone seems to use default ("type 2") decimation */
+ ch->decimation = VADC_DEF_DECIMATION;
+ /* Everyone seems to use this 1-to-1 premux scaling channel */
+ ch->amux_mpp_channel = MPP_SCALE_0;
+
+ if (!of_property_read_u32(np, "qcom,ratiometric", &rsv)) {
+ ch->calibration = VADC_CALIB_RATIOMETRIC;
+ if (rsv > XOADC_RSV_MAX) {
+ dev_err(dev, "%s too large RSV value %d\n", name, rsv);
+ return -EINVAL;
+ }
+ if (rsv == AMUX_RSV3) {
+ dev_err(dev, "%s invalid RSV value %d\n", name, rsv);
+ return -EINVAL;
+ }
+ }
+
+ /* Optional decimation, if omitted we use the default */
+ ret = of_property_read_u32(np, "qcom,decimation", &dec);
+ if (!ret) {
+ ret = qcom_vadc_decimation_from_dt(dec);
+ if (ret < 0) {
+ dev_err(dev, "%s invalid decimation %d\n",
+ name, dec);
+ return ret;
+ }
+ ch->decimation = ret;
+ }
+
+ iio_chan->channel = ch->amux_channel;
+ iio_chan->address = ch->amux_channel;
+ iio_chan->datasheet_name = ch->name;
+ iio_chan->type = hw_channels[chan].type;
+ /* All channels are raw or processed */
+ iio_chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_PROCESSED);
+ iio_chan->indexed = 1;
+
+ dev_dbg(dev, "channel %d \"%s\" ref voltage: %d, decimation %d "
+ "prescale %d/%d, scale function %d\n",
+ ch->amux_channel, ch->name, ch->amux_ip_rsv,
+ ch->decimation, ch->prescale->num, ch->prescale->den,
+ ch->scale_fn_type);
+
+ return 0;
+}
+
+static int pm8xxx_xoadc_parse_channels(struct pm8xxx_xoadc *adc,
+ struct device_node *np)
+{
+ struct device_node *child;
+ struct pm8xxx_chan_info *ch;
+ int ret;
+ int i;
+
+ adc->nchans = of_get_available_child_count(np);
+ if (!adc->nchans) {
+ dev_err(adc->dev, "no channel children\n");
+ return -ENODEV;
+ }
+ dev_dbg(adc->dev, "found %d ADC channels\n", adc->nchans);
+
+ adc->iio_chans = devm_kcalloc(adc->dev, adc->nchans,
+ sizeof(*adc->iio_chans), GFP_KERNEL);
+ if (!adc->iio_chans)
+ return -ENOMEM;
+
+ adc->chans = devm_kcalloc(adc->dev, adc->nchans,
+ sizeof(*adc->chans), GFP_KERNEL);
+ if (!adc->chans)
+ return -ENOMEM;
+
+ i = 0;
+ for_each_available_child_of_node(np, child) {
+ ch = &adc->chans[i];
+ ret = pm8xxx_xoadc_parse_channel(adc->dev, child,
+ adc->hw_channels,
+ &adc->iio_chans[i],
+ ch);
+ if (ret) {
+ of_node_put(child);
+ return ret;
+ }
+ i++;
+ }
+
+ /* Check for required channels */
+ ch = pm8xxx_get_channel(adc, CHANNEL_125V);
+ if (!ch) {
+ dev_err(adc->dev, "missing 1.25V reference channel\n");
+ return -ENODEV;
+ }
+ ch = pm8xxx_get_channel(adc, CHANNEL_INTERNAL);
+ if (!ch) {
+ dev_err(adc->dev, "missing 0.625V reference channel\n");
+ return -ENODEV;
+ }
+ ch = pm8xxx_get_channel(adc, CHANNEL_MUXOFF);
+ if (!ch) {
+ dev_err(adc->dev, "missing MUXOFF reference channel\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int pm8xxx_xoadc_probe(struct platform_device *pdev)
+{
+ struct pm8xxx_xoadc *adc;
+ struct iio_dev *indio_dev;
+ struct device_node *np = pdev->dev.of_node;
+ struct regmap *map;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
+ if (!indio_dev)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, indio_dev);
+
+ adc = iio_priv(indio_dev);
+ adc->dev = dev;
+ init_completion(&adc->complete);
+ mutex_init(&adc->lock);
+
+ /* Look up variant data */
+ adc->hw_channels = of_device_get_match_data(dev);
+ if (!adc->hw_channels) {
+ dev_err(dev, "missing variant data\n");
+ return -ENODEV;
+ }
+
+ /* This variant has special bugs that we need to deal with */
+ if (of_device_is_compatible(np, "qcom,pm8058-adc"))
+ adc->is_pm8058 = true;
+
+ ret = pm8xxx_xoadc_parse_channels(adc, np);
+ if (ret)
+ return ret;
+
+ map = dev_get_regmap(dev->parent, NULL);
+ if (!map) {
+ dev_err(dev, "parent regmap unavailable.\n");
+ return -ENXIO;
+ }
+ adc->map = map;
+
+ /* Bring up regulator */
+ adc->vref = devm_regulator_get(dev, "xoadc-ref");
+ if (IS_ERR(adc->vref)) {
+ dev_err(dev, "failed to get XOADC VREF regulator\n");
+ return PTR_ERR(adc->vref);
+ }
+ ret = regulator_enable(adc->vref);
+ if (ret) {
+ dev_err(dev, "failed to enable XOADC VREF regulator\n");
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
+ pm8xxx_eoc_irq, NULL, 0, "pm8xxx-adc", indio_dev);
+ if (ret) {
+ dev_err(dev, "unable to request IRQ\n");
+ goto out_disable_vref;
+ }
+
+ indio_dev->dev.parent = dev;
+ indio_dev->dev.of_node = np;
+ indio_dev->name = "pm8xxx-xoadc";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &pm8xxx_xoadc_info;
+ indio_dev->channels = adc->iio_chans;
+ indio_dev->num_channels = adc->nchans;
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto out_disable_vref;
+
+ ret = pm8xxx_calibrate_device(adc);
+ if (ret)
+ goto out_unreg_device;
+
+ dev_info(dev, "PM8xxx XOADC driver enabled\n");
+
+ return 0;
+
+out_unreg_device:
+ iio_device_unregister(indio_dev);
+out_disable_vref:
+ regulator_disable(adc->vref);
+
+ return ret;
+}
+
+static int pm8xxx_xoadc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct pm8xxx_xoadc *adc = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ regulator_disable(adc->vref);
+
+ return 0;
+}
+
+static const struct of_device_id pm8xxx_xoadc_id_table[] = {
+ {
+ .compatible = "qcom,pm8018-adc",
+ .data = &pm8018_xoadc_channels,
+ },
+ {
+ .compatible = "qcom,pm8038-adc",
+ .data = &pm8038_xoadc_channels,
+ },
+ {
+ .compatible = "qcom,pm8058-adc",
+ .data = &pm8058_xoadc_channels,
+ },
+ {
+ .compatible = "qcom,pm8921-adc",
+ .data = &pm8921_xoadc_channels,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pm8xxx_xoadc_id_table);
+
+static struct platform_driver pm8xxx_xoadc_driver = {
+ .driver = {
+ .name = "pm8xxx-adc",
+ .of_match_table = pm8xxx_xoadc_id_table,
+ },
+ .probe = pm8xxx_xoadc_probe,
+ .remove = pm8xxx_xoadc_remove,
+};
+module_platform_driver(pm8xxx_xoadc_driver);
+
+MODULE_DESCRIPTION("PM8xxx XOADC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:pm8xxx-xoadc");
--
2.9.3
^ permalink raw reply related
* [PATCH 3/3 v2] iio: adc: add a driver for Qualcomm PM8xxx HK/XOADC
From: Linus Walleij @ 2017-01-17 14:35 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20161227065814.GC10531@minitux>
On Tue, Dec 27, 2016 at 7:58 AM, Bjorn Andersson
<bjorn.andersson@linaro.org> wrote:
> On Thu 15 Dec 14:48 PST 2016, Linus Walleij wrote:
> I gave this a spin on one of my APQ8064 devices - with PM8921 - and I
> can do measurements.
Nice :) I hope the v3 patchset also gives something, hopefully more.
> Several of the channels do give me reasonable readings. But the channels
> in PM8921 are different than PM8058, so it looks like we need additional
> prescales defined. Further more, the 8921 has several different
> thermo-related channels, each with their own (post-)scaling function.
I hope I have fixed this for all variants in the v3 version.
> And lastly it seems that I have 2 thermo-channels on "mpp amux", but I'm
> still confused to how that is supposed to work in the downstream code -
> I will need to investigate this further.
>
> One of the channels defined in my APQ8064 device measures
> ADC_MPP_1_AMUX5, a lookup table is used to convert the measured value to
> a temperature. As the table depends on an external thermistor, which
> seems to differ between products a number of lookup tables exists - with
> 156 data points each.
(...)
> On my 8064 device I have 6 different temperatures (2 coming from amux'ed
> mpp channels). They all have different lookup tables, which seems
> calibrated based on some external resistor.
Rama Krishna cleared it up a bit, then I have tried to make it even
clearer.
Essentially all MPP (multi-purpose pins indeed) can have any random
analog value coming in, but there are some layers of enums on top
of the actual hardware channels confusing things a bit.
Some MPPs are used for reading battery temperature and system
temperature in some systems, but this is essentially a board-specific
use of a general purpose ADC and need to be above the driver, however
I tried to encode the most typical usecases a bit in the different PMIC
channel tables, so that MPP8 is usually set up a temperature channel.
We need additional bindings to specify conversion functions from the
device tree but that can be made on top of this patch set as an
expansion.
The approach chosen here is that this ADC driver should present
"processed" (scaled etc) values, and to achieve this the scaling is
done directly on the raw values read from the ADC, which are
first scaled to uV from calibration and then scaled to e.g. a
temperature.
This is all clearer after Rama Krishna's patches.
>> + /* We strictly require this voltage */
>> + ret = regulator_set_voltage(adc->vref, 2200000, 2200000);
>
> On 8064 the reference is L14 and it's at 1.8V, so this fails for me.
I have removes this hard setting of voltage.
Yours,
Linus Walleij
^ permalink raw reply
* [PATCH 2/4] musb: sunxi: add support for the variant in H3/V3s SoC
From: Bin Liu @ 2017-01-17 14:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170106135715.y7cwrwqyzybl6t5m@lukather>
On Fri, Jan 06, 2017 at 02:57:15PM +0100, Maxime Ripard wrote:
> On Tue, Jan 03, 2017 at 11:25:32PM +0800, Icenowy Zheng wrote:
> > Allwinner H3/V3s features a variant of MUSB controller, which lacks one
> > endpoint.
> >
> > Add support for it.
> >
> > Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
>
> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Applied. Thanks.
-Bin.
^ permalink raw reply
* [PATCH v2 2/3] spi: pxa2xx: Prepare for edge-triggered interrupts
From: Jan Kiszka @ 2017-01-17 14:43 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <d6d9079e-f671-139e-42d8-75158ac9ee17@linux.intel.com>
On 2017-01-17 14:11, Jarkko Nikula wrote:
> On 01/17/2017 10:10 AM, Jan Kiszka wrote:
>> On 2017-01-17 08:58, Robert Jarzmik wrote:
>>> Jan Kiszka <jan.kiszka@siemens.com> writes:
>>>
>>>> When using the a device with edge-triggered interrupts, such as MSIs,
>>>> the interrupt handler has to ensure that there is a point in time
>>>> during
>>>> its execution where all interrupts sources are silent so that a new
>>>> event can trigger a new interrupt again.
>>>>
>>>> This is achieved here by looping over SSSR evaluation. We need to take
>>>> into account that SSCR1 may be changed by the transfer handler, thus we
>>>> need to redo the mask calculation, at least regarding the volatile
>>>> interrupt enable bit (TIE).
>>>
>>> I'd like moreover to add a question here.
>>>
>>> In pxa architecture, SPI interrupts are already edge-triggered, and
>>> it's working
>>> well. The interrupt source disabling is not disabled, but the interrupt
>>> controller doesn't trigger an interrupt anymore (as it is masked),
>>> yet it marks
>>> it as pending if an interrupt arrives while the interrupt handler is
>>> running.
>>>
>>> All of this is handled by the interrupt core. My question is why for
>>> Intel MSI's
>>> is it necessary to make a change in the driver instead or relying on the
>>> interrupt core as for the pxa ?
>>
>> If someone was using this driver with edge-triggered interrupt sources
>> so far, it was probably slower hardware and some luck (I've seen this
>> when driving fast-clocked devices vs. slower ones - only the latter
>> exposed the bug). Or that hardware did some temporary masking at
>> interrupt controller level while the handler was running. But that is
>> also not by design. It's the driver's task to ensure that all interrupt
>> sources are addressed once when returning from an edge-triggered
>> handler, and that is missing in this one.
>>
> Are you seeing actual problem here or adding loop just in case? Is it
> really so that PCI bridge doesn't generate another MSI interrupt if SPI
> controller has interrupt pending when handler returns? I don't know but
> I would expect irq line between SPI controller and PCI bridge is still
> level sensitive even PCI bridge issues MSIs to the CPU.
>
Yes, I'm seeing real problems, e.g. on the Galileo board when running
against a slower SPI device and using MSI: An interrupt is raised
because the TX queue was flushed towards the device (threshold
underrun). While handling that interrupt, the device starts to respond
and an RX event occurs as well. This raises the related interrupt reason
before the TX source was satisfied. Therefore, the interrupt output of
the SPI master will never go down, and there will be no additional edge
generated. Using level-interrupts, this is no problem, but with
edge-triggered we get stuck.
Jan
--
Siemens AG, Corporate Technology, CT RDA ITP SES-DE
Corporate Competence Center Embedded Linux
^ permalink raw reply
* [PATCH v4 4/4] arm64: dts: juno: add missing CoreSight STM component
From: Mike Leach @ 2017-01-17 14:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <6473418b-95d5-5be3-47fd-4c554f353923@arm.com>
----------------------------------------------------------------
Mike Leach +44 (0)1254 893911 (Direct)
Principal Engineer +44 (0)1254 893900 (Main)
Arm Blackburn Design Centre +44 (0)1254 893901 (Fax)
Belthorn House
Walker Rd mailto:mike.leach at arm.com
Guide
Blackburn
BB1 2QE
----------------------------------------------------------------
> -----Original Message-----
> From: Sudeep Holla [mailto:sudeep.holla at arm.com]
> Sent: 17 January 2017 14:01
> To: Suzuki Poulose; linux-arm-kernel at lists.infradead.org; Mike Leach; Mike
> Leach
> Cc: Sudeep Holla; Mathieu Poirier; Lorenzo Pieralisi; coresight at lists.linaro.org;
> Liviu Dudau; devicetree at vger.kernel.org; Olof Johansson
> Subject: Re: [PATCH v4 4/4] arm64: dts: juno: add missing CoreSight STM
> component
>
>
>
> On 17/01/17 13:39, Suzuki K Poulose wrote:
> > On 17/01/17 12:15, Sudeep Holla wrote:
> >> From: Mike Leach <mike.leach@linaro.org>
> >>
> >> This patch adds the missing CoreSight STM component definition to the
> >> device tree of all the juno variants(r0,r1,r2)
> >>
> >> STM component is connected to different funnels depending on Juno
> >> platform variant.
> >>
> >> Reviewed-and-Tested-by: Mathieu Poirier <mathieu.poirier@linaro.org>
> >> Signed-off-by: Mike Leach <mike.leach@linaro.org>
> >> [sudeep.holla at arm.com: minor changelog update and reorganising the STM
> >> node back into juno-base.dtsi to avoid duplication]
> >> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> >> ---
> >> arch/arm64/boot/dts/arm/juno-base.dtsi | 15 +++++++++++++++
> >> arch/arm64/boot/dts/arm/juno-r1.dts | 4 ++++
> >> arch/arm64/boot/dts/arm/juno-r2.dts | 4 ++++
> >> arch/arm64/boot/dts/arm/juno.dts | 16 ++++++++++++++++
> >> 4 files changed, 39 insertions(+)
> >>
> >> diff --git a/arch/arm64/boot/dts/arm/juno-base.dtsi
> >> b/arch/arm64/boot/dts/arm/juno-base.dtsi
> >> index faedf357db3b..58c1773c3aa4 100644
> >> --- a/arch/arm64/boot/dts/arm/juno-base.dtsi
> >> +++ b/arch/arm64/boot/dts/arm/juno-base.dtsi
> >> @@ -185,6 +185,21 @@
> >> };
> >> };
> >>
> >> + stm at 20100000 {
> >> + compatible = "arm,coresight-stm", "arm,primecell";
> >> + reg = <0 0x20100000 0 0x1000>,
> >> + <0 0x28000000 0 0x180000>;
> >
> > The size of the stimulus base is 16MB (as per TRM), implies => 0x1000000.
> >
>
> Fixed locally.
>
> Mike,
>
> Can you confirm ? I know TRM had some issues with size of other
> coresight components.
>
Checked both Juno and STM arch manuals - these are in agreement that the STM memory stimulus area is 16MB in size.
Thus for Juno specifically with all masters seeing the same area @ base 0x28000000, <0 0x28000000 0 0x1000000 > would be the correct description.
Mike
> > with that fixed :
> >
> > Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
> >
>
> Thanks.
>
> --
> Regards,
> Sudeep
IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.
^ permalink raw reply
* [PATCH v2] ata: xgene: Enable NCQ support for APM X-Gene SATA controller hardware v1.1
From: Rameshwar Sahu @ 2017-01-17 14:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1479462345-13942-1-git-send-email-rsahu@apm.com>
Hi Tejun,
On Fri, Nov 18, 2016 at 3:15 PM, Rameshwar Prasad Sahu <rsahu@apm.com> wrote:
> This patch enables NCQ support for APM X-Gene SATA controller hardware v1.1
> that was broken with hardware v1.0. Second thing, here we should not assume
> XGENE_AHCI_V2 always in case of having valid _CID in ACPI table. I need to
> remove this assumption because V1_1 also has a valid _CID for backward
> compatibly with v1.
>
> v2 changes:
> 1. Changed patch description
>
> Signed-off-by: Rameshwar Prasad Sahu <rsahu@apm.com>
> ---
> drivers/ata/ahci_xgene.c | 14 ++++++++------
> 1 files changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/ata/ahci_xgene.c b/drivers/ata/ahci_xgene.c
> index 73b19b2..8b88be9 100644
> --- a/drivers/ata/ahci_xgene.c
> +++ b/drivers/ata/ahci_xgene.c
> @@ -87,6 +87,7 @@
>
> enum xgene_ahci_version {
> XGENE_AHCI_V1 = 1,
> + XGENE_AHCI_V1_1,
> XGENE_AHCI_V2,
> };
>
> @@ -734,6 +735,7 @@ static struct scsi_host_template ahci_platform_sht = {
> #ifdef CONFIG_ACPI
> static const struct acpi_device_id xgene_ahci_acpi_match[] = {
> { "APMC0D0D", XGENE_AHCI_V1},
> + { "APMC0D67", XGENE_AHCI_V1_1},
> { "APMC0D32", XGENE_AHCI_V2},
> {},
> };
> @@ -742,6 +744,7 @@ MODULE_DEVICE_TABLE(acpi, xgene_ahci_acpi_match);
>
> static const struct of_device_id xgene_ahci_of_match[] = {
> {.compatible = "apm,xgene-ahci", .data = (void *) XGENE_AHCI_V1},
> + {.compatible = "apm,xgene-ahci-v1-1", .data = (void *) XGENE_AHCI_V1_1},
> {.compatible = "apm,xgene-ahci-v2", .data = (void *) XGENE_AHCI_V2},
> {},
> };
> @@ -755,8 +758,7 @@ static int xgene_ahci_probe(struct platform_device *pdev)
> struct resource *res;
> const struct of_device_id *of_devid;
> enum xgene_ahci_version version = XGENE_AHCI_V1;
> - const struct ata_port_info *ppi[] = { &xgene_ahci_v1_port_info,
> - &xgene_ahci_v2_port_info };
> + const struct ata_port_info *ppi;
> int rc;
>
> hpriv = ahci_platform_get_resources(pdev);
> @@ -821,8 +823,6 @@ static int xgene_ahci_probe(struct platform_device *pdev)
> dev_warn(&pdev->dev, "%s: Error reading device info. Assume version1\n",
> __func__);
> version = XGENE_AHCI_V1;
> - } else if (info->valid & ACPI_VALID_CID) {
> - version = XGENE_AHCI_V2;
> }
> }
> }
> @@ -858,18 +858,20 @@ skip_clk_phy:
>
> switch (version) {
> case XGENE_AHCI_V1:
> + ppi = &xgene_ahci_v1_port_info;
> hpriv->flags = AHCI_HFLAG_NO_NCQ;
> break;
> case XGENE_AHCI_V2:
> + ppi = &xgene_ahci_v2_port_info;
> hpriv->flags |= AHCI_HFLAG_YES_FBS;
> hpriv->irq_handler = xgene_ahci_irq_intr;
> break;
> default:
> + ppi = &xgene_ahci_v1_port_info;
> break;
> }
>
> - rc = ahci_platform_init_host(pdev, hpriv, ppi[version - 1],
> - &ahci_platform_sht);
> + rc = ahci_platform_init_host(pdev, hpriv, ppi, &ahci_platform_sht);
> if (rc)
> goto disable_resources;
>
> --
> 1.7.1
>
Any comments on this patch ??
^ permalink raw reply
* [PATCH v4 4/4] arm64: dts: juno: add missing CoreSight STM component
From: Sudeep Holla @ 2017-01-17 14:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <DB6PR0802MB2198D30880E268077202AAD88C7C0@DB6PR0802MB2198.eurprd08.prod.outlook.com>
On 17/01/17 14:52, Mike Leach wrote:
[...]
>>
>> Mike,
>>
>> Can you confirm ? I know TRM had some issues with size of other
>> coresight components.
>>
> Checked both Juno and STM arch manuals - these are in agreement that
> the STM memory stimulus area is 16MB in size. Thus for Juno
> specifically with all masters seeing the same area @ base 0x28000000,
> <0 0x28000000 0 0x1000000 > would be the correct description.
>
Thanks for the confirmation, I have already made the changes in [1]
--
Regards,
Sudeep
[1] git.kernel.org/sudeep.holla/linux/h/for-next/updates/juno
^ permalink raw reply
* [PATCH v2 0/2] Add USB DRD 3.0 device node for Exynos7
From: Pankaj Dubey @ 2017-01-17 15:04 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CGME20170117150119epcas1p30b5cfc4e4be8f0dc300fa587e6cac7f1@epcas1p3.samsung.com>
This patch series adds USB 3.0 DRD device node for Exynos7 and
required Vbus and Vbus-boost support for espresso dev boards.
These patches are part of patch series [1] earlier submitted by Vivek
[1]: https://lkml.org/lkml/2014/11/21/247
While other patches in these series got merged, DTS related change were
left. I have prepared and tested these patches on top of krzk/for-next.
Changes since v1:
- Updated S-o-B order and Authorship of patches.
- Added Javier's Reviewed-by.
- Replaced GPIO polarity values with appropriate macros.
Vivek Gautam (2):
arm64: dts: exynos: Add USB 3.0 controller node for Exynos7
arm64: dts: exynos: Add regulators for Vbus and Vbus-Boost
arch/arm64/boot/dts/exynos/exynos7-espresso.dts | 45 +++++++++++++++++++++++++
arch/arm64/boot/dts/exynos/exynos7.dtsi | 34 +++++++++++++++++++
2 files changed, 79 insertions(+)
--
2.7.4
^ 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