All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 0/2] add support for Allwinner SoCs ADC
@ 2016-09-15 12:44 ` Quentin Schulz
  0 siblings, 0 replies; 14+ messages in thread
From: Quentin Schulz @ 2016-09-15 12:44 UTC (permalink / raw)
  To: jdelvare, linux, jic23, knaack.h, lars, pmeerw, maxime.ripard,
	wens, lee.jones
  Cc: Quentin Schulz, thomas.petazzoni, antoine.tenart, linux-kernel,
	linux-hwmon, linux-iio, linux-arm-kernel

The Allwinner SoCs all have an ADC that can also act as a touchscreen
controller and a thermal sensor. The first four channels can be used either
for the ADC or the touchscreen and the fifth channel is used for the
thermal sensor. We currently have a driver for the two latter functions in
drivers/input/touchscreen/sun4i-ts.c but we don't have access to the ADC
feature at all. It is meant to replace the current driver by using MFD and
subdrivers.

This adds initial support for Allwinner SoCs ADC with all features. Yet,
the touchscreen is not implemented but will be added later. To switch
between touchscreen and ADC modes, you need to poke a few bits in registers
and (de)activate an interrupt (pen-up).
An MFD is provided to let the input driver activate the pen-up interrupt
through a virtual interrupt, poke a few bits via regmap and read data from
the ADC driver while both (and iio_hwmon) are probed by the MFD.

There are slight variations between the different SoCs ADC like the address
of some registers and the scale and offset to apply to raw thermal sensor
values. These variations are handled by using different platform_device_id,
passed to the sub-drivers when they are probed by the MFD.

Removal of proposed patch for iio_hwmon's iio channel's label in v3. The
patch induces irreversible ABI changes and will be handled as a separate
patch since I think it is not absolutely necessary to have labels yet in
iio_hwmon.

Removal of proposed patch for reattaching of_node of the MFD to the MFD
cell device structure in v3. As Lee Jones said, this patch might cause
"unintended side-effects for existing drivers.". Moreover, this patch
introduced a bug of multiple probe of this MFD driver I haven't identified
yet. This patch aimed at allowing the ADC driver (which is a child of the
MFD and not present in the DT) to register in the thermal framework. The
thermal driver has a phandle to the MFD node which is used to match against
the MFD of_node but since the ADC driver has no node in the DT, could not
register in the thermal framework. The other solution is to "impersonate"
the MFD when registering in the thermal framework since the device is only
used to match the phandle and the of_node, an other structure passed by
parameter being used to compute temperatures.

(in the ADC driver, probed by the MFD driver) instead of:
tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, info,
					   &sun4i_ts_tz_ops);
we now have:
tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
					   &sun4i_ts_tz_ops);

Removal of proposed patch to use late_initcall for iio_hwmon probe deferring.

Removal of patch for iio_hwmon probe deferring due to being applied to -next by
Guenter Roeck.

Quentin Schulz (2):
  mfd: add support for Allwinner SoCs ADC
  iio: adc: add support for Allwinner SoCs ADC

 drivers/iio/adc/Kconfig           |  13 +
 drivers/iio/adc/Makefile          |   1 +
 drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig               |  15 ++
 drivers/mfd/Makefile              |   2 +
 drivers/mfd/sun4i-gpadc.c         | 181 +++++++++++++
 include/linux/mfd/sun4i-gpadc.h   |  94 +++++++
 7 files changed, 828 insertions(+)
 create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
 create mode 100644 drivers/mfd/sun4i-gpadc.c
 create mode 100644 include/linux/mfd/sun4i-gpadc.h

-- 
2.5.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v6 0/2] add support for Allwinner SoCs ADC
@ 2016-09-15 12:44 ` Quentin Schulz
  0 siblings, 0 replies; 14+ messages in thread
From: Quentin Schulz @ 2016-09-15 12:44 UTC (permalink / raw)
  To: linux-arm-kernel

The Allwinner SoCs all have an ADC that can also act as a touchscreen
controller and a thermal sensor. The first four channels can be used either
for the ADC or the touchscreen and the fifth channel is used for the
thermal sensor. We currently have a driver for the two latter functions in
drivers/input/touchscreen/sun4i-ts.c but we don't have access to the ADC
feature at all. It is meant to replace the current driver by using MFD and
subdrivers.

This adds initial support for Allwinner SoCs ADC with all features. Yet,
the touchscreen is not implemented but will be added later. To switch
between touchscreen and ADC modes, you need to poke a few bits in registers
and (de)activate an interrupt (pen-up).
An MFD is provided to let the input driver activate the pen-up interrupt
through a virtual interrupt, poke a few bits via regmap and read data from
the ADC driver while both (and iio_hwmon) are probed by the MFD.

There are slight variations between the different SoCs ADC like the address
of some registers and the scale and offset to apply to raw thermal sensor
values. These variations are handled by using different platform_device_id,
passed to the sub-drivers when they are probed by the MFD.

Removal of proposed patch for iio_hwmon's iio channel's label in v3. The
patch induces irreversible ABI changes and will be handled as a separate
patch since I think it is not absolutely necessary to have labels yet in
iio_hwmon.

Removal of proposed patch for reattaching of_node of the MFD to the MFD
cell device structure in v3. As Lee Jones said, this patch might cause
"unintended side-effects for existing drivers.". Moreover, this patch
introduced a bug of multiple probe of this MFD driver I haven't identified
yet. This patch aimed at allowing the ADC driver (which is a child of the
MFD and not present in the DT) to register in the thermal framework. The
thermal driver has a phandle to the MFD node which is used to match against
the MFD of_node but since the ADC driver has no node in the DT, could not
register in the thermal framework. The other solution is to "impersonate"
the MFD when registering in the thermal framework since the device is only
used to match the phandle and the of_node, an other structure passed by
parameter being used to compute temperatures.

(in the ADC driver, probed by the MFD driver) instead of:
tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, info,
					   &sun4i_ts_tz_ops);
we now have:
tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
					   &sun4i_ts_tz_ops);

Removal of proposed patch to use late_initcall for iio_hwmon probe deferring.

Removal of patch for iio_hwmon probe deferring due to being applied to -next by
Guenter Roeck.

Quentin Schulz (2):
  mfd: add support for Allwinner SoCs ADC
  iio: adc: add support for Allwinner SoCs ADC

 drivers/iio/adc/Kconfig           |  13 +
 drivers/iio/adc/Makefile          |   1 +
 drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig               |  15 ++
 drivers/mfd/Makefile              |   2 +
 drivers/mfd/sun4i-gpadc.c         | 181 +++++++++++++
 include/linux/mfd/sun4i-gpadc.h   |  94 +++++++
 7 files changed, 828 insertions(+)
 create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
 create mode 100644 drivers/mfd/sun4i-gpadc.c
 create mode 100644 include/linux/mfd/sun4i-gpadc.h

-- 
2.5.0

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v6 1/2] mfd: add support for Allwinner SoCs ADC
  2016-09-15 12:44 ` Quentin Schulz
@ 2016-09-15 12:44   ` Quentin Schulz
  -1 siblings, 0 replies; 14+ messages in thread
From: Quentin Schulz @ 2016-09-15 12:44 UTC (permalink / raw)
  To: jdelvare, linux, jic23, knaack.h, lars, pmeerw, maxime.ripard,
	wens, lee.jones
  Cc: Quentin Schulz, thomas.petazzoni, antoine.tenart, linux-kernel,
	linux-hwmon, linux-iio, linux-arm-kernel

The Allwinner SoCs all have an ADC that can also act as a touchscreen
controller and a thermal sensor. For now, only the ADC and the thermal
sensor drivers are probed by the MFD, the touchscreen controller support
will be added later.

Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Acked-by: Jonathan Cameron <jic23@kernel.org>
---

v6:
 - remove "-mfd" from filenames and variables inside MFD driver,
 - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
 - cosmetic changes,
 - use IDs and switch over ID to get cells specific to an architecture, instead
 of using cells direclty, in of_device_id.data,
 - compute size of mfd_cells array instead of hardcoded one,

v5:
 - correct mail address,

v4:
 - rename files and variables from sunxi* to sun4i*,
 - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
 - remove TP in defines name,
 - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
 - use devm functions for regmap_add_irq_chip and mfd_add_devices,
 - remove remove functions (now empty thanks to devm functions),

v3:
 - use defines in regmap_irq instead of hard coded BITs,
 - use of_device_id data field to chose which MFD cells to add considering
   the compatible responsible of the MFD probe,
 - remove useless initializations,
 - disable all interrupts before adding them to regmap_irqchip,
 - add goto error label in probe,
 - correct wrapping in header license,
 - move defines from IIO driver to header,
 - use GENMASK to limit the size of the variable passed to a macro,
 - prefix register BIT defines with the name of the register,
 - reorder defines,

v2:
 - add license headers,
 - reorder alphabetically includes,
 - add SUNXI_GPADC_ prefixes for defines,

 drivers/mfd/Kconfig             |  15 ++++
 drivers/mfd/Makefile            |   2 +
 drivers/mfd/sun4i-gpadc.c       | 181 ++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/sun4i-gpadc.h |  94 +++++++++++++++++++++
 4 files changed, 292 insertions(+)
 create mode 100644 drivers/mfd/sun4i-gpadc.c
 create mode 100644 include/linux/mfd/sun4i-gpadc.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 1bcf601..45db3f4 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -29,6 +29,21 @@ config MFD_ACT8945A
 	  linear regulators, along with a complete ActivePath battery
 	  charger.
 
+config MFD_SUN4I_GPADC
+	tristate "Allwinner sunxi platforms' GPADC MFD driver"
+	select MFD_CORE
+	select REGMAP_MMIO
+	depends on ARCH_SUNXI || COMPILE_TEST
+	help
+	  Select this to get support for Allwinner SoCs (A10, A13 and A31) ADC.
+	  This driver will only map the hardware interrupt and registers, you
+	  have to select individual drivers based on this MFD to be able to use
+	  the ADC or the thermal sensor. This will try to probe the ADC driver
+	  sun4i-gpadc-iio and the hwmon driver iio_hwmon.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called sun4i-gpadc.
+
 config MFD_AS3711
 	bool "AMS AS3711"
 	select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 42a66e1..12c49af 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -205,3 +205,5 @@ intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
 intel-soc-pmic-$(CONFIG_INTEL_PMC_IPC)	+= intel_soc_pmic_bxtwc.o
 obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
 obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o
+
+obj-$(CONFIG_MFD_SUN4I_GPADC)	+= sun4i-gpadc.o
diff --git a/drivers/mfd/sun4i-gpadc.c b/drivers/mfd/sun4i-gpadc.c
new file mode 100644
index 0000000..276a1f4
--- /dev/null
+++ b/drivers/mfd/sun4i-gpadc.c
@@ -0,0 +1,181 @@
+/* ADC MFD core driver for sunxi platforms
+ *
+ * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/sun4i-gpadc.h>
+
+#define ARCH_SUN4I_A10 0
+#define ARCH_SUN5I_A13 1
+#define ARCH_SUN6I_A31 2
+
+static struct resource adc_resources[] = {
+	DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_FIFO_DATA, "FIFO_DATA_PENDING"),
+	DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_TEMP_DATA, "TEMP_DATA_PENDING"),
+};
+
+static const struct regmap_irq sun4i_gpadc_regmap_irq[] = {
+	REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_FIFO_DATA, 0,
+		       SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN),
+	REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_TEMP_DATA, 0,
+		       SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN),
+};
+
+static const struct regmap_irq_chip sun4i_gpadc_regmap_irq_chip = {
+	.name = "sun4i_gpadc_irq_chip",
+	.status_base = SUN4I_GPADC_INT_FIFOS,
+	.ack_base = SUN4I_GPADC_INT_FIFOS,
+	.mask_base = SUN4I_GPADC_INT_FIFOC,
+	.init_ack_masked = true,
+	.mask_invert = true,
+	.irqs = sun4i_gpadc_regmap_irq,
+	.num_irqs = ARRAY_SIZE(sun4i_gpadc_regmap_irq),
+	.num_regs = 1,
+};
+
+static struct mfd_cell sun4i_gpadc_cells[] = {
+	{
+		.name	= "sun4i-a10-gpadc-iio",
+		.resources = adc_resources,
+		.num_resources = ARRAY_SIZE(adc_resources),
+	},
+	{ .name = "iio_hwmon" }
+};
+
+static struct mfd_cell sun5i_gpadc_cells[] = {
+	{
+		.name	= "sun5i-a13-gpadc-iio",
+		.resources = adc_resources,
+		.num_resources = ARRAY_SIZE(adc_resources),
+	},
+	{ .name = "iio_hwmon" },
+};
+
+static struct mfd_cell sun6i_gpadc_cells[] = {
+	{
+		.name	= "sun6i-a31-gpadc-iio",
+		.resources = adc_resources,
+		.num_resources = ARRAY_SIZE(adc_resources),
+	},
+	{ .name = "iio_hwmon" },
+};
+
+static const struct regmap_config sun4i_gpadc_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.fast_io = true,
+};
+
+static const struct of_device_id sun4i_gpadc_of_match[] = {
+	{
+		.compatible = "allwinner,sun4i-a10-ts",
+		.data = (void *)ARCH_SUN4I_A10,
+	}, {
+		.compatible = "allwinner,sun5i-a13-ts",
+		.data = (void *)ARCH_SUN5I_A13,
+	}, {
+		.compatible = "allwinner,sun6i-a31-ts",
+		.data = (void *)ARCH_SUN6I_A31,
+	}, { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, sun4i_gpadc_of_match);
+
+static int sun4i_gpadc_probe(struct platform_device *pdev)
+{
+	struct sun4i_gpadc_dev *dev;
+	struct resource *mem;
+	const struct of_device_id *of_id;
+	const struct mfd_cell *cells;
+	unsigned int irq, size;
+	int ret;
+
+	of_id = of_match_node(sun4i_gpadc_of_match, pdev->dev.of_node);
+	if (!of_id)
+		return -EINVAL;
+
+	switch ((int)of_id->data) {
+	case ARCH_SUN4I_A10:
+		cells = sun4i_gpadc_cells;
+		size = ARRAY_SIZE(sun4i_gpadc_cells);
+		break;
+	case ARCH_SUN5I_A13:
+		cells = sun5i_gpadc_cells;
+		size = ARRAY_SIZE(sun5i_gpadc_cells);
+		break;
+	case ARCH_SUN6I_A31:
+		cells = sun6i_gpadc_cells;
+		size = ARRAY_SIZE(sun6i_gpadc_cells);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dev->base = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(dev->base))
+		return PTR_ERR(dev->base);
+
+	dev->dev = &pdev->dev;
+	dev_set_drvdata(dev->dev, dev);
+
+	dev->regmap = devm_regmap_init_mmio(dev->dev, dev->base,
+					    &sun4i_gpadc_regmap_config);
+	if (IS_ERR(dev->regmap)) {
+		ret = PTR_ERR(dev->regmap);
+		dev_err(&pdev->dev, "failed to init regmap: %d\n", ret);
+		return ret;
+	}
+
+	/* Disable all interrupts */
+	regmap_write(dev->regmap, SUN4I_GPADC_INT_FIFOC, 0);
+
+	irq = platform_get_irq(pdev, 0);
+	ret = devm_regmap_add_irq_chip(&pdev->dev, dev->regmap, irq,
+				       IRQF_ONESHOT, 0,
+				       &sun4i_gpadc_regmap_irq_chip,
+				       &dev->regmap_irqc);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to add irq chip: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_mfd_add_devices(dev->dev, 0, cells, size, NULL, 0, NULL);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to add MFD devices: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct platform_driver sun4i_gpadc_driver = {
+	.driver = {
+		.name = "sun4i-gpadc",
+		.of_match_table = of_match_ptr(sun4i_gpadc_of_match),
+	},
+	.probe = sun4i_gpadc_probe,
+};
+
+module_platform_driver(sun4i_gpadc_driver);
+
+MODULE_DESCRIPTION("Allwinner sunxi platforms' GPADC MFD core driver");
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
new file mode 100644
index 0000000..d7a29f2
--- /dev/null
+++ b/include/linux/mfd/sun4i-gpadc.h
@@ -0,0 +1,94 @@
+/* Header of ADC MFD core driver for sunxi platforms
+ *
+ * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#ifndef __SUN4I_GPADC__H__
+#define __SUN4I_GPADC__H__
+
+#define SUN4I_GPADC_CTRL0				0x00
+
+#define SUN4I_GPADC_CTRL0_ADC_FIRST_DLY(x)		((GENMASK(7, 0) & (x)) << 24)
+#define SUN4I_GPADC_CTRL0_ADC_FIRST_DLY_MODE		BIT(23)
+#define SUN4I_GPADC_CTRL0_ADC_CLK_SELECT		BIT(22)
+#define SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(x)		((GENMASK(1, 0) & (x)) << 20)
+#define SUN4I_GPADC_CTRL0_FS_DIV(x)			((GENMASK(3, 0) & (x)) << 16)
+#define SUN4I_GPADC_CTRL0_T_ACQ(x)			(GENMASK(15, 0) & (x))
+
+#define SUN4I_GPADC_CTRL1				0x04
+
+#define SUN4I_GPADC_CTRL1_STYLUS_UP_DEBOUNCE(x)		((GENMASK(7, 0) & (x)) << 12)
+#define SUN4I_GPADC_CTRL1_STYLUS_UP_DEBOUNCE_EN		BIT(9)
+#define SUN4I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(6)
+#define SUN4I_GPADC_CTRL1_TP_DUAL_EN			BIT(5)
+#define SUN4I_GPADC_CTRL1_TP_MODE_EN			BIT(4)
+#define SUN4I_GPADC_CTRL1_TP_ADC_SELECT			BIT(3)
+#define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(2, 0) & (x))
+
+/* TP_CTRL1 bits for sun6i SOCs */
+#define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(7)
+#define SUN6I_GPADC_CTRL1_TP_DUAL_EN			BIT(6)
+#define SUN6I_GPADC_CTRL1_TP_MODE_EN			BIT(5)
+#define SUN6I_GPADC_CTRL1_TP_ADC_SELECT			BIT(4)
+#define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(3, 0) & BIT(x))
+
+#define SUN4I_GPADC_CTRL2				0x08
+
+#define SUN4I_GPADC_CTRL2_TP_SENSITIVE_ADJUST(x)	((GENMASK(3, 0) & (x)) << 28)
+#define SUN4I_GPADC_CTRL2_TP_MODE_SELECT(x)		((GENMASK(1, 0) & (x)) << 26)
+#define SUN4I_GPADC_CTRL2_PRE_MEA_EN			BIT(24)
+#define SUN4I_GPADC_CTRL2_PRE_MEA_THRE_CNT(x)		(GENMASK(23, 0) & (x))
+
+#define SUN4I_GPADC_CTRL3				0x0c
+
+#define SUN4I_GPADC_CTRL3_FILTER_EN			BIT(2)
+#define SUN4I_GPADC_CTRL3_FILTER_TYPE(x)		(GENMASK(1, 0) & (x))
+
+#define SUN4I_GPADC_TPR					0x18
+
+#define SUN4I_GPADC_TPR_TEMP_ENABLE			BIT(16)
+#define SUN4I_GPADC_TPR_TEMP_PERIOD(x)			(GENMASK(15, 0) & (x))
+
+#define SUN4I_GPADC_INT_FIFOC				0x10
+
+#define SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN		BIT(18)
+#define SUN4I_GPADC_INT_FIFOC_TP_OVERRUN_IRQ_EN		BIT(17)
+#define SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN		BIT(16)
+#define SUN4I_GPADC_INT_FIFOC_TP_DATA_XY_CHANGE		BIT(13)
+#define SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(x)	((GENMASK(4, 0) & (x)) << 8)
+#define SUN4I_GPADC_INT_FIFOC_TP_DATA_DRQ_EN		BIT(7)
+#define SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH		BIT(4)
+#define SUN4I_GPADC_INT_FIFOC_TP_UP_IRQ_EN		BIT(1)
+#define SUN4I_GPADC_INT_FIFOC_TP_DOWN_IRQ_EN		BIT(0)
+
+#define SUN4I_GPADC_INT_FIFOS				0x14
+
+#define SUN4I_GPADC_INT_FIFOS_TEMP_DATA_PENDING		BIT(18)
+#define SUN4I_GPADC_INT_FIFOS_FIFO_OVERRUN_PENDING	BIT(17)
+#define SUN4I_GPADC_INT_FIFOS_FIFO_DATA_PENDING		BIT(16)
+#define SUN4I_GPADC_INT_FIFOS_TP_IDLE_FLG		BIT(2)
+#define SUN4I_GPADC_INT_FIFOS_TP_UP_PENDING		BIT(1)
+#define SUN4I_GPADC_INT_FIFOS_TP_DOWN_PENDING		BIT(0)
+
+#define SUN4I_GPADC_CDAT				0x1c
+#define SUN4I_GPADC_TEMP_DATA				0x20
+#define SUN4I_GPADC_DATA				0x24
+
+#define SUN4I_GPADC_IRQ_FIFO_DATA			0
+#define SUN4I_GPADC_IRQ_TEMP_DATA			1
+
+/* 10s delay before suspending the IP */
+#define SUN4I_GPADC_AUTOSUSPEND_DELAY			10000
+
+struct sun4i_gpadc_dev {
+	struct device			*dev;
+	struct regmap			*regmap;
+	struct regmap_irq_chip_data	*regmap_irqc;
+	void __iomem			*base;
+};
+
+#endif
-- 
2.5.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v6 1/2] mfd: add support for Allwinner SoCs ADC
@ 2016-09-15 12:44   ` Quentin Schulz
  0 siblings, 0 replies; 14+ messages in thread
From: Quentin Schulz @ 2016-09-15 12:44 UTC (permalink / raw)
  To: linux-arm-kernel

The Allwinner SoCs all have an ADC that can also act as a touchscreen
controller and a thermal sensor. For now, only the ADC and the thermal
sensor drivers are probed by the MFD, the touchscreen controller support
will be added later.

Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Acked-by: Jonathan Cameron <jic23@kernel.org>
---

v6:
 - remove "-mfd" from filenames and variables inside MFD driver,
 - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
 - cosmetic changes,
 - use IDs and switch over ID to get cells specific to an architecture, instead
 of using cells direclty, in of_device_id.data,
 - compute size of mfd_cells array instead of hardcoded one,

v5:
 - correct mail address,

v4:
 - rename files and variables from sunxi* to sun4i*,
 - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
 - remove TP in defines name,
 - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
 - use devm functions for regmap_add_irq_chip and mfd_add_devices,
 - remove remove functions (now empty thanks to devm functions),

v3:
 - use defines in regmap_irq instead of hard coded BITs,
 - use of_device_id data field to chose which MFD cells to add considering
   the compatible responsible of the MFD probe,
 - remove useless initializations,
 - disable all interrupts before adding them to regmap_irqchip,
 - add goto error label in probe,
 - correct wrapping in header license,
 - move defines from IIO driver to header,
 - use GENMASK to limit the size of the variable passed to a macro,
 - prefix register BIT defines with the name of the register,
 - reorder defines,

v2:
 - add license headers,
 - reorder alphabetically includes,
 - add SUNXI_GPADC_ prefixes for defines,

 drivers/mfd/Kconfig             |  15 ++++
 drivers/mfd/Makefile            |   2 +
 drivers/mfd/sun4i-gpadc.c       | 181 ++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/sun4i-gpadc.h |  94 +++++++++++++++++++++
 4 files changed, 292 insertions(+)
 create mode 100644 drivers/mfd/sun4i-gpadc.c
 create mode 100644 include/linux/mfd/sun4i-gpadc.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 1bcf601..45db3f4 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -29,6 +29,21 @@ config MFD_ACT8945A
 	  linear regulators, along with a complete ActivePath battery
 	  charger.
 
+config MFD_SUN4I_GPADC
+	tristate "Allwinner sunxi platforms' GPADC MFD driver"
+	select MFD_CORE
+	select REGMAP_MMIO
+	depends on ARCH_SUNXI || COMPILE_TEST
+	help
+	  Select this to get support for Allwinner SoCs (A10, A13 and A31) ADC.
+	  This driver will only map the hardware interrupt and registers, you
+	  have to select individual drivers based on this MFD to be able to use
+	  the ADC or the thermal sensor. This will try to probe the ADC driver
+	  sun4i-gpadc-iio and the hwmon driver iio_hwmon.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called sun4i-gpadc.
+
 config MFD_AS3711
 	bool "AMS AS3711"
 	select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 42a66e1..12c49af 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -205,3 +205,5 @@ intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
 intel-soc-pmic-$(CONFIG_INTEL_PMC_IPC)	+= intel_soc_pmic_bxtwc.o
 obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
 obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o
+
+obj-$(CONFIG_MFD_SUN4I_GPADC)	+= sun4i-gpadc.o
diff --git a/drivers/mfd/sun4i-gpadc.c b/drivers/mfd/sun4i-gpadc.c
new file mode 100644
index 0000000..276a1f4
--- /dev/null
+++ b/drivers/mfd/sun4i-gpadc.c
@@ -0,0 +1,181 @@
+/* ADC MFD core driver for sunxi platforms
+ *
+ * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/sun4i-gpadc.h>
+
+#define ARCH_SUN4I_A10 0
+#define ARCH_SUN5I_A13 1
+#define ARCH_SUN6I_A31 2
+
+static struct resource adc_resources[] = {
+	DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_FIFO_DATA, "FIFO_DATA_PENDING"),
+	DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_TEMP_DATA, "TEMP_DATA_PENDING"),
+};
+
+static const struct regmap_irq sun4i_gpadc_regmap_irq[] = {
+	REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_FIFO_DATA, 0,
+		       SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN),
+	REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_TEMP_DATA, 0,
+		       SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN),
+};
+
+static const struct regmap_irq_chip sun4i_gpadc_regmap_irq_chip = {
+	.name = "sun4i_gpadc_irq_chip",
+	.status_base = SUN4I_GPADC_INT_FIFOS,
+	.ack_base = SUN4I_GPADC_INT_FIFOS,
+	.mask_base = SUN4I_GPADC_INT_FIFOC,
+	.init_ack_masked = true,
+	.mask_invert = true,
+	.irqs = sun4i_gpadc_regmap_irq,
+	.num_irqs = ARRAY_SIZE(sun4i_gpadc_regmap_irq),
+	.num_regs = 1,
+};
+
+static struct mfd_cell sun4i_gpadc_cells[] = {
+	{
+		.name	= "sun4i-a10-gpadc-iio",
+		.resources = adc_resources,
+		.num_resources = ARRAY_SIZE(adc_resources),
+	},
+	{ .name = "iio_hwmon" }
+};
+
+static struct mfd_cell sun5i_gpadc_cells[] = {
+	{
+		.name	= "sun5i-a13-gpadc-iio",
+		.resources = adc_resources,
+		.num_resources = ARRAY_SIZE(adc_resources),
+	},
+	{ .name = "iio_hwmon" },
+};
+
+static struct mfd_cell sun6i_gpadc_cells[] = {
+	{
+		.name	= "sun6i-a31-gpadc-iio",
+		.resources = adc_resources,
+		.num_resources = ARRAY_SIZE(adc_resources),
+	},
+	{ .name = "iio_hwmon" },
+};
+
+static const struct regmap_config sun4i_gpadc_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.fast_io = true,
+};
+
+static const struct of_device_id sun4i_gpadc_of_match[] = {
+	{
+		.compatible = "allwinner,sun4i-a10-ts",
+		.data = (void *)ARCH_SUN4I_A10,
+	}, {
+		.compatible = "allwinner,sun5i-a13-ts",
+		.data = (void *)ARCH_SUN5I_A13,
+	}, {
+		.compatible = "allwinner,sun6i-a31-ts",
+		.data = (void *)ARCH_SUN6I_A31,
+	}, { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, sun4i_gpadc_of_match);
+
+static int sun4i_gpadc_probe(struct platform_device *pdev)
+{
+	struct sun4i_gpadc_dev *dev;
+	struct resource *mem;
+	const struct of_device_id *of_id;
+	const struct mfd_cell *cells;
+	unsigned int irq, size;
+	int ret;
+
+	of_id = of_match_node(sun4i_gpadc_of_match, pdev->dev.of_node);
+	if (!of_id)
+		return -EINVAL;
+
+	switch ((int)of_id->data) {
+	case ARCH_SUN4I_A10:
+		cells = sun4i_gpadc_cells;
+		size = ARRAY_SIZE(sun4i_gpadc_cells);
+		break;
+	case ARCH_SUN5I_A13:
+		cells = sun5i_gpadc_cells;
+		size = ARRAY_SIZE(sun5i_gpadc_cells);
+		break;
+	case ARCH_SUN6I_A31:
+		cells = sun6i_gpadc_cells;
+		size = ARRAY_SIZE(sun6i_gpadc_cells);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dev->base = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(dev->base))
+		return PTR_ERR(dev->base);
+
+	dev->dev = &pdev->dev;
+	dev_set_drvdata(dev->dev, dev);
+
+	dev->regmap = devm_regmap_init_mmio(dev->dev, dev->base,
+					    &sun4i_gpadc_regmap_config);
+	if (IS_ERR(dev->regmap)) {
+		ret = PTR_ERR(dev->regmap);
+		dev_err(&pdev->dev, "failed to init regmap: %d\n", ret);
+		return ret;
+	}
+
+	/* Disable all interrupts */
+	regmap_write(dev->regmap, SUN4I_GPADC_INT_FIFOC, 0);
+
+	irq = platform_get_irq(pdev, 0);
+	ret = devm_regmap_add_irq_chip(&pdev->dev, dev->regmap, irq,
+				       IRQF_ONESHOT, 0,
+				       &sun4i_gpadc_regmap_irq_chip,
+				       &dev->regmap_irqc);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to add irq chip: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_mfd_add_devices(dev->dev, 0, cells, size, NULL, 0, NULL);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to add MFD devices: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct platform_driver sun4i_gpadc_driver = {
+	.driver = {
+		.name = "sun4i-gpadc",
+		.of_match_table = of_match_ptr(sun4i_gpadc_of_match),
+	},
+	.probe = sun4i_gpadc_probe,
+};
+
+module_platform_driver(sun4i_gpadc_driver);
+
+MODULE_DESCRIPTION("Allwinner sunxi platforms' GPADC MFD core driver");
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
new file mode 100644
index 0000000..d7a29f2
--- /dev/null
+++ b/include/linux/mfd/sun4i-gpadc.h
@@ -0,0 +1,94 @@
+/* Header of ADC MFD core driver for sunxi platforms
+ *
+ * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#ifndef __SUN4I_GPADC__H__
+#define __SUN4I_GPADC__H__
+
+#define SUN4I_GPADC_CTRL0				0x00
+
+#define SUN4I_GPADC_CTRL0_ADC_FIRST_DLY(x)		((GENMASK(7, 0) & (x)) << 24)
+#define SUN4I_GPADC_CTRL0_ADC_FIRST_DLY_MODE		BIT(23)
+#define SUN4I_GPADC_CTRL0_ADC_CLK_SELECT		BIT(22)
+#define SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(x)		((GENMASK(1, 0) & (x)) << 20)
+#define SUN4I_GPADC_CTRL0_FS_DIV(x)			((GENMASK(3, 0) & (x)) << 16)
+#define SUN4I_GPADC_CTRL0_T_ACQ(x)			(GENMASK(15, 0) & (x))
+
+#define SUN4I_GPADC_CTRL1				0x04
+
+#define SUN4I_GPADC_CTRL1_STYLUS_UP_DEBOUNCE(x)		((GENMASK(7, 0) & (x)) << 12)
+#define SUN4I_GPADC_CTRL1_STYLUS_UP_DEBOUNCE_EN		BIT(9)
+#define SUN4I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(6)
+#define SUN4I_GPADC_CTRL1_TP_DUAL_EN			BIT(5)
+#define SUN4I_GPADC_CTRL1_TP_MODE_EN			BIT(4)
+#define SUN4I_GPADC_CTRL1_TP_ADC_SELECT			BIT(3)
+#define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(2, 0) & (x))
+
+/* TP_CTRL1 bits for sun6i SOCs */
+#define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(7)
+#define SUN6I_GPADC_CTRL1_TP_DUAL_EN			BIT(6)
+#define SUN6I_GPADC_CTRL1_TP_MODE_EN			BIT(5)
+#define SUN6I_GPADC_CTRL1_TP_ADC_SELECT			BIT(4)
+#define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(3, 0) & BIT(x))
+
+#define SUN4I_GPADC_CTRL2				0x08
+
+#define SUN4I_GPADC_CTRL2_TP_SENSITIVE_ADJUST(x)	((GENMASK(3, 0) & (x)) << 28)
+#define SUN4I_GPADC_CTRL2_TP_MODE_SELECT(x)		((GENMASK(1, 0) & (x)) << 26)
+#define SUN4I_GPADC_CTRL2_PRE_MEA_EN			BIT(24)
+#define SUN4I_GPADC_CTRL2_PRE_MEA_THRE_CNT(x)		(GENMASK(23, 0) & (x))
+
+#define SUN4I_GPADC_CTRL3				0x0c
+
+#define SUN4I_GPADC_CTRL3_FILTER_EN			BIT(2)
+#define SUN4I_GPADC_CTRL3_FILTER_TYPE(x)		(GENMASK(1, 0) & (x))
+
+#define SUN4I_GPADC_TPR					0x18
+
+#define SUN4I_GPADC_TPR_TEMP_ENABLE			BIT(16)
+#define SUN4I_GPADC_TPR_TEMP_PERIOD(x)			(GENMASK(15, 0) & (x))
+
+#define SUN4I_GPADC_INT_FIFOC				0x10
+
+#define SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN		BIT(18)
+#define SUN4I_GPADC_INT_FIFOC_TP_OVERRUN_IRQ_EN		BIT(17)
+#define SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN		BIT(16)
+#define SUN4I_GPADC_INT_FIFOC_TP_DATA_XY_CHANGE		BIT(13)
+#define SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(x)	((GENMASK(4, 0) & (x)) << 8)
+#define SUN4I_GPADC_INT_FIFOC_TP_DATA_DRQ_EN		BIT(7)
+#define SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH		BIT(4)
+#define SUN4I_GPADC_INT_FIFOC_TP_UP_IRQ_EN		BIT(1)
+#define SUN4I_GPADC_INT_FIFOC_TP_DOWN_IRQ_EN		BIT(0)
+
+#define SUN4I_GPADC_INT_FIFOS				0x14
+
+#define SUN4I_GPADC_INT_FIFOS_TEMP_DATA_PENDING		BIT(18)
+#define SUN4I_GPADC_INT_FIFOS_FIFO_OVERRUN_PENDING	BIT(17)
+#define SUN4I_GPADC_INT_FIFOS_FIFO_DATA_PENDING		BIT(16)
+#define SUN4I_GPADC_INT_FIFOS_TP_IDLE_FLG		BIT(2)
+#define SUN4I_GPADC_INT_FIFOS_TP_UP_PENDING		BIT(1)
+#define SUN4I_GPADC_INT_FIFOS_TP_DOWN_PENDING		BIT(0)
+
+#define SUN4I_GPADC_CDAT				0x1c
+#define SUN4I_GPADC_TEMP_DATA				0x20
+#define SUN4I_GPADC_DATA				0x24
+
+#define SUN4I_GPADC_IRQ_FIFO_DATA			0
+#define SUN4I_GPADC_IRQ_TEMP_DATA			1
+
+/* 10s delay before suspending the IP */
+#define SUN4I_GPADC_AUTOSUSPEND_DELAY			10000
+
+struct sun4i_gpadc_dev {
+	struct device			*dev;
+	struct regmap			*regmap;
+	struct regmap_irq_chip_data	*regmap_irqc;
+	void __iomem			*base;
+};
+
+#endif
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v6 2/2] iio: adc: add support for Allwinner SoCs ADC
  2016-09-15 12:44 ` Quentin Schulz
@ 2016-09-15 12:44   ` Quentin Schulz
  -1 siblings, 0 replies; 14+ messages in thread
From: Quentin Schulz @ 2016-09-15 12:44 UTC (permalink / raw)
  To: jdelvare, linux, jic23, knaack.h, lars, pmeerw, maxime.ripard,
	wens, lee.jones
  Cc: Quentin Schulz, thomas.petazzoni, antoine.tenart, linux-kernel,
	linux-hwmon, linux-iio, linux-arm-kernel

The Allwinner SoCs all have an ADC that can also act as a touchscreen
controller and a thermal sensor. This patch adds the ADC driver which is
based on the MFD for the same SoCs ADC.

This also registers the thermal adc channel in the iio map array so
iio_hwmon could use it without modifying the Device Tree. This registers
the driver in the thermal framework.

This driver probes on three different platform_device_id to take into
account slight differences (registers bit and temperature computation)
between Allwinner SoCs ADCs.

Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Acked-by: Jonathan Cameron <jic23@kernel.org>
---

v6:
 - remove useless member (regs) from sun4i_gpadc_dev structure,
 - rename sun4i_gpadc_dev structure to sun4i_gpadc_iio,
 - remove regmap_update_bits used to disable hardware interrupts, it is already
 handled by devm functions,

v5:
 - correct mail address,
 - correct several typos,
 - move from const to static for sunxi_gpadc_chan_select functions,
 - rename soc_specific struct to gpadc_data,
 - rename soc_specific field to data in sun4i_gpadc_dev,
 - return error code from regmap_write in case of failure in read_raws,
 - share if condition in IIO_CHAN_INFO_RAW case,
 - add comment on why we use parent device for registering in thermal,
 - reordering remove function,

v4:
 - rename files and variables from sunxi* to sun4i*,
 - shorten sunxi_gpadc_soc_specific structure to soc_specific,
 - factorize sysfs ADC and temp read_raws,
 - use cached values when read_raw times out (except before a first value
   is gotten),
 - remove mutex locks and unlocks from runtime_pm functions,
 - factorize irq initializations,
 - initialize temp_data and fifo_data values to -1 (error value),
 - "impersonate" MFD to register in thermal framework,
 - deactivate hardware interrupts one by one when probe fails or when
   removing driver instead of blindly deactivating all hardware interrupts,
 - selects THERMAL_OF in Kconfig,

v3:
 - correct wrapping,
 - add comment about thermal sensor inner working,
 - move defines in mfd header,
 - use structure to define SoC specific registers or behaviour,
 - attach this structure to the device according to of_device_id of the
   platform device,
 - use new mutex instead of iio_dev mutex,
 - use atomic flags to avoid race between request_irq and disable_irq in
   probe,
 - switch from processed value to raw, offset and scale values for
   temperature ADC channel,
 - remove faulty sentinel in iio_chan_spec array,
 - add pm_runtime support,
 - register thermal sensor in thermal framework (forgotten since the
   beginning whereas it is present in current sun4i-ts driver),
 - remove useless ret variables to store return value of regmap_reads,
 - move comments on thermal sensor acquisition period in code instead of
   header,
 - adding goto label to unregister iio_map_array when failing to register
   iio_dev,

v2:
 - add SUNXI_GPADC_ prefixes for defines,
 - correct typo in Kconfig,
 - reorder alphabetically includes, makefile,
 - add license header,
 - fix architecture variations not being handled in interrupt handlers or
   read raw functions,
 - fix unability to return negative values from thermal sensor,
 - add gotos to reduce code repetition,
 - fix irq variable being unsigned int instead of int,
 - remove useless dev_err and dev_info,
 - deactivate all interrupts if probe fails,
 - fix iio_device_register on NULL variable,
 - deactivate ADC in the IP when probe fails or when removing driver,

 drivers/iio/adc/Kconfig           |  13 +
 drivers/iio/adc/Makefile          |   1 +
 drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 536 insertions(+)
 create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 25378c5..ea36a4f 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -384,6 +384,19 @@ config ROCKCHIP_SARADC
 	  To compile this driver as a module, choose M here: the
 	  module will be called rockchip_saradc.
 
+config SUN4I_GPADC
+	tristate "Support for the Allwinner SoCs GPADC"
+	depends on IIO
+	depends on MFD_SUN4I_GPADC
+	select THERMAL_OF
+	help
+	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
+	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
+	  a touchscreen input and one channel for thermal sensor.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called sun4i-gpadc-iio.
+
 config TI_ADC081C
 	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
 	depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 38638d4..204372d 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -37,6 +37,7 @@ 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
 obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
+obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
 obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
 obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
 obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
new file mode 100644
index 0000000..e2c5ba8
--- /dev/null
+++ b/drivers/iio/adc/sun4i-gpadc-iio.c
@@ -0,0 +1,522 @@
+/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
+ *
+ * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
+ * Free Software Foundation.
+ *
+ * The Allwinner SoCs all have an ADC that can also act as a touchscreen
+ * controller and a thermal sensor.
+ * The thermal sensor works only when the ADC acts as a touchscreen controller
+ * and is configured to throw an interrupt every fixed periods of time (let say
+ * every X seconds).
+ * One would be tempted to disable the IP on the hardware side rather than
+ * disabling interrupts to save some power but that resets the internal clock of
+ * the IP, resulting in having to wait X seconds every time we want to read the
+ * value of the thermal sensor.
+ * This is also the reason of using autosuspend in pm_runtime. If there was no
+ * autosuspend, the thermal sensor would need X seconds after every
+ * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
+ * thermal sensor to be requested again in a certain time span before it gets
+ * shutdown for not being used.
+ */
+
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/driver.h>
+#include <linux/iio/machine.h>
+#include <linux/mfd/sun4i-gpadc.h>
+
+static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
+{
+	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
+}
+
+static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
+{
+	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
+}
+
+struct gpadc_data {
+	int		temp_offset;
+	int		temp_scale;
+	unsigned int	tp_mode_en;
+	unsigned int	tp_adc_select;
+	unsigned int	(*adc_chan_select)(unsigned int chan);
+};
+
+static const struct gpadc_data sun4i_gpadc_data = {
+	.temp_offset = -1932,
+	.temp_scale = 133,
+	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
+	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
+	.adc_chan_select = &sun4i_gpadc_chan_select,
+};
+
+static const struct gpadc_data sun5i_gpadc_data = {
+	.temp_offset = -1447,
+	.temp_scale = 100,
+	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
+	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
+	.adc_chan_select = &sun4i_gpadc_chan_select,
+};
+
+static const struct gpadc_data sun6i_gpadc_data = {
+	.temp_offset = -1623,
+	.temp_scale = 167,
+	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
+	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
+	.adc_chan_select = &sun6i_gpadc_chan_select,
+};
+
+struct sun4i_gpadc_iio {
+	struct iio_dev			*indio_dev;
+	struct completion		completion;
+	int				temp_data;
+	u32				adc_data;
+	struct regmap			*regmap;
+	unsigned int			fifo_data_irq;
+	atomic_t			ignore_fifo_data_irq;
+	unsigned int			temp_data_irq;
+	atomic_t			ignore_temp_data_irq;
+	const struct gpadc_data		*data;
+	/* prevents concurrent reads of temperature and ADC */
+	struct mutex			mutex;
+};
+
+#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
+	.type = IIO_VOLTAGE,					\
+	.indexed = 1,						\
+	.channel = _channel,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
+	.datasheet_name = _name,				\
+}
+
+static struct iio_map sun4i_gpadc_hwmon_maps[] = {
+	{
+		.adc_channel_label = "temp_adc",
+		.consumer_dev_name = "iio_hwmon.0",
+	},
+	{ /* sentinel */ },
+};
+
+static const struct iio_chan_spec sun4i_gpadc_channels[] = {
+	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
+	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
+	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
+	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_OFFSET),
+		.datasheet_name = "temp_adc",
+	},
+};
+
+static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
+			    unsigned int irq)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+	int ret = 0;
+
+	pm_runtime_get_sync(indio_dev->dev.parent);
+	mutex_lock(&info->mutex);
+
+	reinit_completion(&info->completion);
+
+	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
+			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
+			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
+	if (ret)
+		return ret;
+
+	if (irq == info->fifo_data_irq) {
+		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
+				   info->data->tp_mode_en |
+				   info->data->tp_adc_select |
+				   info->data->adc_chan_select(channel));
+	} else {
+		/*
+		 * The temperature sensor returns valid data only when the ADC
+		 * operates in touchscreen mode.
+		 */
+		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
+				   info->data->tp_mode_en);
+	}
+
+	if (ret)
+		return ret;
+
+	enable_irq(irq);
+
+	if (!wait_for_completion_timeout(&info->completion,
+					 msecs_to_jiffies(100))) {
+		if ((irq == info->fifo_data_irq && info->adc_data == -1) ||
+		    (irq == info->temp_data_irq && info->temp_data == -1)) {
+			ret = -ETIMEDOUT;
+			goto out;
+		}
+	}
+
+	if (irq == info->fifo_data_irq)
+		*val = info->adc_data;
+	else
+		*val = info->temp_data;
+
+	ret = 0;
+
+out:
+	disable_irq(irq);
+	mutex_unlock(&info->mutex);
+	pm_runtime_mark_last_busy(indio_dev->dev.parent);
+	pm_runtime_put_autosuspend(indio_dev->dev.parent);
+
+	return ret;
+}
+
+static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
+				int *val)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
+}
+
+static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
+}
+
+static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+	*val = info->data->temp_offset;
+
+	return 0;
+}
+
+static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+	*val = info->data->temp_scale;
+
+	return 0;
+}
+
+static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan, int *val,
+				int *val2, long mask)
+{
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_OFFSET:
+		ret = sun4i_gpadc_temp_offset(indio_dev, val);
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type == IIO_VOLTAGE)
+			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
+						   val);
+		else
+			ret = sun4i_gpadc_temp_read(indio_dev, val);
+
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		ret = sun4i_gpadc_temp_scale(indio_dev, val);
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+static const struct iio_info sun4i_gpadc_iio_info = {
+	.read_raw = sun4i_gpadc_read_raw,
+	.driver_module = THIS_MODULE,
+};
+
+static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
+{
+	struct sun4i_gpadc_iio *info = dev_id;
+
+	if (atomic_read(&info->ignore_temp_data_irq))
+		return IRQ_HANDLED;
+
+	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
+		complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
+{
+	struct sun4i_gpadc_iio *info = dev_id;
+
+	if (atomic_read(&info->ignore_fifo_data_irq))
+		return IRQ_HANDLED;
+
+	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
+		complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int sun4i_gpadc_runtime_suspend(struct device *dev)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
+
+	/* Disable the ADC on IP */
+	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
+	/* Disable temperature sensor on IP */
+	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
+
+	return 0;
+}
+
+static int sun4i_gpadc_runtime_resume(struct device *dev)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
+
+	/* clkin = 6MHz */
+	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
+		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
+		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
+		     SUN4I_GPADC_CTRL0_T_ACQ(63));
+	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
+	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
+		     SUN4I_GPADC_CTRL3_FILTER_EN |
+		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
+	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~1.3s */
+	regmap_write(info->regmap, SUN4I_GPADC_TPR,
+		     SUN4I_GPADC_TPR_TEMP_ENABLE |
+		     SUN4I_GPADC_TPR_TEMP_PERIOD(1953));
+
+	return 0;
+}
+
+static int sun4i_gpadc_get_temp(void *data, int *temp)
+{
+	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
+	int val, scale, offset;
+
+	/* If reading temperature times out, take stored previous value. */
+	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
+		val = info->temp_data;
+	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
+	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
+
+	*temp = (val + offset) * scale;
+
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
+	.get_temp = &sun4i_gpadc_get_temp,
+};
+
+static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
+	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
+	.runtime_resume = &sun4i_gpadc_runtime_resume,
+};
+
+static int sun4i_irq_init(struct platform_device *pdev, const char *name,
+			  irq_handler_t handler, const char *devname,
+			  unsigned int *irq, atomic_t *atomic)
+{
+	int ret;
+	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
+	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
+
+	/*
+	 * Once the interrupt is activated, the IP continuously performs
+	 * conversions thus throws interrupts. The interrupt is activated right
+	 * after being requested but we want to control when these interrupts
+	 * occur thus we disable it right after being requested. However, an
+	 * interrupt might occur between these two instructions and we have to
+	 * make sure that does not happen, by using atomic flags. We set the
+	 * flag before requesting the interrupt and unset it right after
+	 * disabling the interrupt. When an interrupt occurs between these two
+	 * instructions, reading the atomic flag will tell us to ignore the
+	 * interrupt.
+	 */
+	atomic_set(atomic, 1);
+
+	*irq = platform_get_irq_byname(pdev, name);
+	if (*irq < 0) {
+		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
+		return *irq;
+	}
+
+	*irq = regmap_irq_get_virq(mfd_dev->regmap_irqc, *irq);
+	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
+					   devname, info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
+			name, ret);
+		return ret;
+	}
+
+	disable_irq(*irq);
+	atomic_set(atomic, 0);
+
+	return 0;
+}
+
+static int sun4i_gpadc_probe(struct platform_device *pdev)
+{
+	struct sun4i_gpadc_iio *info;
+	struct iio_dev *indio_dev;
+	int ret;
+	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
+	struct thermal_zone_device *tzd;
+
+	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	info = iio_priv(indio_dev);
+	platform_set_drvdata(pdev, indio_dev);
+
+	mutex_init(&info->mutex);
+	info->regmap = sun4i_gpadc_dev->regmap;
+	info->indio_dev = indio_dev;
+	info->temp_data = -1;
+	info->adc_data = -1;
+	init_completion(&info->completion);
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->info = &sun4i_gpadc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
+	indio_dev->channels = sun4i_gpadc_channels;
+
+	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
+
+	/*
+	 * This driver is a child of an MFD which has a node in the DT but not
+	 * its children. Therefore, the resulting devices of this driver do not
+	 * have an of_node variable.
+	 * However, its parent (the MFD driver) has an of_node variable and
+	 * since devm_thermal_zone_of_sensor_register uses its first argument to
+	 * match the phandle defined in the node of the thermal driver with the
+	 * of_node of the device passed as first argument and the third argument
+	 * to call ops from thermal_zone_of_device_ops, the solution is to use
+	 * the parent device as first argument to match the phandle with its
+	 * of_node, and the device from this driver as third argument to return
+	 * the temperature.
+	 */
+	tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
+						   &sun4i_ts_tz_ops);
+	if (IS_ERR(tzd)) {
+		dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
+			PTR_ERR(tzd));
+		return PTR_ERR(tzd);
+	}
+
+	pm_runtime_set_autosuspend_delay(&pdev->dev,
+					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
+	pm_runtime_use_autosuspend(&pdev->dev);
+	pm_runtime_set_suspended(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
+			     sun4i_gpadc_temp_data_irq_handler, "temp_data",
+			     &info->temp_data_irq, &info->ignore_temp_data_irq);
+	if (ret < 0)
+		goto err;
+
+	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
+			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
+			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
+	if (ret < 0)
+		goto err;
+
+	ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to register iio map array\n");
+		goto err;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "could not register the device\n");
+		goto err_map;
+	}
+
+	return 0;
+
+err_map:
+	iio_map_array_unregister(indio_dev);
+
+err:
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	return ret;
+}
+
+static int sun4i_gpadc_remove(struct platform_device *pdev)
+{
+	struct sun4i_gpadc_iio *info;
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	info = iio_priv(indio_dev);
+	iio_map_array_unregister(indio_dev);
+	iio_device_unregister(indio_dev);
+
+	return 0;
+}
+
+static const struct platform_device_id sun4i_gpadc_id[] = {
+	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
+	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
+	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
+	{ /* sentinel */ },
+};
+
+static struct platform_driver sun4i_gpadc_driver = {
+	.driver = {
+		.name = "sun4i-gpadc-iio",
+		.pm = &sun4i_gpadc_pm_ops,
+	},
+	.id_table = sun4i_gpadc_id,
+	.probe = sun4i_gpadc_probe,
+	.remove = sun4i_gpadc_remove,
+};
+
+module_platform_driver(sun4i_gpadc_driver);
+
+MODULE_DESCRIPTION("ADC driver for sunxi platforms");
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.5.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v6 2/2] iio: adc: add support for Allwinner SoCs ADC
@ 2016-09-15 12:44   ` Quentin Schulz
  0 siblings, 0 replies; 14+ messages in thread
From: Quentin Schulz @ 2016-09-15 12:44 UTC (permalink / raw)
  To: linux-arm-kernel

The Allwinner SoCs all have an ADC that can also act as a touchscreen
controller and a thermal sensor. This patch adds the ADC driver which is
based on the MFD for the same SoCs ADC.

This also registers the thermal adc channel in the iio map array so
iio_hwmon could use it without modifying the Device Tree. This registers
the driver in the thermal framework.

This driver probes on three different platform_device_id to take into
account slight differences (registers bit and temperature computation)
between Allwinner SoCs ADCs.

Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Acked-by: Jonathan Cameron <jic23@kernel.org>
---

v6:
 - remove useless member (regs) from sun4i_gpadc_dev structure,
 - rename sun4i_gpadc_dev structure to sun4i_gpadc_iio,
 - remove regmap_update_bits used to disable hardware interrupts, it is already
 handled by devm functions,

v5:
 - correct mail address,
 - correct several typos,
 - move from const to static for sunxi_gpadc_chan_select functions,
 - rename soc_specific struct to gpadc_data,
 - rename soc_specific field to data in sun4i_gpadc_dev,
 - return error code from regmap_write in case of failure in read_raws,
 - share if condition in IIO_CHAN_INFO_RAW case,
 - add comment on why we use parent device for registering in thermal,
 - reordering remove function,

v4:
 - rename files and variables from sunxi* to sun4i*,
 - shorten sunxi_gpadc_soc_specific structure to soc_specific,
 - factorize sysfs ADC and temp read_raws,
 - use cached values when read_raw times out (except before a first value
   is gotten),
 - remove mutex locks and unlocks from runtime_pm functions,
 - factorize irq initializations,
 - initialize temp_data and fifo_data values to -1 (error value),
 - "impersonate" MFD to register in thermal framework,
 - deactivate hardware interrupts one by one when probe fails or when
   removing driver instead of blindly deactivating all hardware interrupts,
 - selects THERMAL_OF in Kconfig,

v3:
 - correct wrapping,
 - add comment about thermal sensor inner working,
 - move defines in mfd header,
 - use structure to define SoC specific registers or behaviour,
 - attach this structure to the device according to of_device_id of the
   platform device,
 - use new mutex instead of iio_dev mutex,
 - use atomic flags to avoid race between request_irq and disable_irq in
   probe,
 - switch from processed value to raw, offset and scale values for
   temperature ADC channel,
 - remove faulty sentinel in iio_chan_spec array,
 - add pm_runtime support,
 - register thermal sensor in thermal framework (forgotten since the
   beginning whereas it is present in current sun4i-ts driver),
 - remove useless ret variables to store return value of regmap_reads,
 - move comments on thermal sensor acquisition period in code instead of
   header,
 - adding goto label to unregister iio_map_array when failing to register
   iio_dev,

v2:
 - add SUNXI_GPADC_ prefixes for defines,
 - correct typo in Kconfig,
 - reorder alphabetically includes, makefile,
 - add license header,
 - fix architecture variations not being handled in interrupt handlers or
   read raw functions,
 - fix unability to return negative values from thermal sensor,
 - add gotos to reduce code repetition,
 - fix irq variable being unsigned int instead of int,
 - remove useless dev_err and dev_info,
 - deactivate all interrupts if probe fails,
 - fix iio_device_register on NULL variable,
 - deactivate ADC in the IP when probe fails or when removing driver,

 drivers/iio/adc/Kconfig           |  13 +
 drivers/iio/adc/Makefile          |   1 +
 drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 536 insertions(+)
 create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 25378c5..ea36a4f 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -384,6 +384,19 @@ config ROCKCHIP_SARADC
 	  To compile this driver as a module, choose M here: the
 	  module will be called rockchip_saradc.
 
+config SUN4I_GPADC
+	tristate "Support for the Allwinner SoCs GPADC"
+	depends on IIO
+	depends on MFD_SUN4I_GPADC
+	select THERMAL_OF
+	help
+	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
+	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
+	  a touchscreen input and one channel for thermal sensor.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called sun4i-gpadc-iio.
+
 config TI_ADC081C
 	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
 	depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 38638d4..204372d 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -37,6 +37,7 @@ 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
 obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
+obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
 obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
 obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
 obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
new file mode 100644
index 0000000..e2c5ba8
--- /dev/null
+++ b/drivers/iio/adc/sun4i-gpadc-iio.c
@@ -0,0 +1,522 @@
+/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
+ *
+ * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
+ * Free Software Foundation.
+ *
+ * The Allwinner SoCs all have an ADC that can also act as a touchscreen
+ * controller and a thermal sensor.
+ * The thermal sensor works only when the ADC acts as a touchscreen controller
+ * and is configured to throw an interrupt every fixed periods of time (let say
+ * every X seconds).
+ * One would be tempted to disable the IP on the hardware side rather than
+ * disabling interrupts to save some power but that resets the internal clock of
+ * the IP, resulting in having to wait X seconds every time we want to read the
+ * value of the thermal sensor.
+ * This is also the reason of using autosuspend in pm_runtime. If there was no
+ * autosuspend, the thermal sensor would need X seconds after every
+ * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
+ * thermal sensor to be requested again in a certain time span before it gets
+ * shutdown for not being used.
+ */
+
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/driver.h>
+#include <linux/iio/machine.h>
+#include <linux/mfd/sun4i-gpadc.h>
+
+static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
+{
+	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
+}
+
+static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
+{
+	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
+}
+
+struct gpadc_data {
+	int		temp_offset;
+	int		temp_scale;
+	unsigned int	tp_mode_en;
+	unsigned int	tp_adc_select;
+	unsigned int	(*adc_chan_select)(unsigned int chan);
+};
+
+static const struct gpadc_data sun4i_gpadc_data = {
+	.temp_offset = -1932,
+	.temp_scale = 133,
+	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
+	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
+	.adc_chan_select = &sun4i_gpadc_chan_select,
+};
+
+static const struct gpadc_data sun5i_gpadc_data = {
+	.temp_offset = -1447,
+	.temp_scale = 100,
+	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
+	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
+	.adc_chan_select = &sun4i_gpadc_chan_select,
+};
+
+static const struct gpadc_data sun6i_gpadc_data = {
+	.temp_offset = -1623,
+	.temp_scale = 167,
+	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
+	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
+	.adc_chan_select = &sun6i_gpadc_chan_select,
+};
+
+struct sun4i_gpadc_iio {
+	struct iio_dev			*indio_dev;
+	struct completion		completion;
+	int				temp_data;
+	u32				adc_data;
+	struct regmap			*regmap;
+	unsigned int			fifo_data_irq;
+	atomic_t			ignore_fifo_data_irq;
+	unsigned int			temp_data_irq;
+	atomic_t			ignore_temp_data_irq;
+	const struct gpadc_data		*data;
+	/* prevents concurrent reads of temperature and ADC */
+	struct mutex			mutex;
+};
+
+#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
+	.type = IIO_VOLTAGE,					\
+	.indexed = 1,						\
+	.channel = _channel,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
+	.datasheet_name = _name,				\
+}
+
+static struct iio_map sun4i_gpadc_hwmon_maps[] = {
+	{
+		.adc_channel_label = "temp_adc",
+		.consumer_dev_name = "iio_hwmon.0",
+	},
+	{ /* sentinel */ },
+};
+
+static const struct iio_chan_spec sun4i_gpadc_channels[] = {
+	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
+	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
+	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
+	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_OFFSET),
+		.datasheet_name = "temp_adc",
+	},
+};
+
+static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
+			    unsigned int irq)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+	int ret = 0;
+
+	pm_runtime_get_sync(indio_dev->dev.parent);
+	mutex_lock(&info->mutex);
+
+	reinit_completion(&info->completion);
+
+	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
+			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
+			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
+	if (ret)
+		return ret;
+
+	if (irq == info->fifo_data_irq) {
+		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
+				   info->data->tp_mode_en |
+				   info->data->tp_adc_select |
+				   info->data->adc_chan_select(channel));
+	} else {
+		/*
+		 * The temperature sensor returns valid data only when the ADC
+		 * operates in touchscreen mode.
+		 */
+		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
+				   info->data->tp_mode_en);
+	}
+
+	if (ret)
+		return ret;
+
+	enable_irq(irq);
+
+	if (!wait_for_completion_timeout(&info->completion,
+					 msecs_to_jiffies(100))) {
+		if ((irq == info->fifo_data_irq && info->adc_data == -1) ||
+		    (irq == info->temp_data_irq && info->temp_data == -1)) {
+			ret = -ETIMEDOUT;
+			goto out;
+		}
+	}
+
+	if (irq == info->fifo_data_irq)
+		*val = info->adc_data;
+	else
+		*val = info->temp_data;
+
+	ret = 0;
+
+out:
+	disable_irq(irq);
+	mutex_unlock(&info->mutex);
+	pm_runtime_mark_last_busy(indio_dev->dev.parent);
+	pm_runtime_put_autosuspend(indio_dev->dev.parent);
+
+	return ret;
+}
+
+static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
+				int *val)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
+}
+
+static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
+}
+
+static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+	*val = info->data->temp_offset;
+
+	return 0;
+}
+
+static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
+
+	*val = info->data->temp_scale;
+
+	return 0;
+}
+
+static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan, int *val,
+				int *val2, long mask)
+{
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_OFFSET:
+		ret = sun4i_gpadc_temp_offset(indio_dev, val);
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_RAW:
+		if (chan->type == IIO_VOLTAGE)
+			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
+						   val);
+		else
+			ret = sun4i_gpadc_temp_read(indio_dev, val);
+
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		ret = sun4i_gpadc_temp_scale(indio_dev, val);
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+static const struct iio_info sun4i_gpadc_iio_info = {
+	.read_raw = sun4i_gpadc_read_raw,
+	.driver_module = THIS_MODULE,
+};
+
+static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
+{
+	struct sun4i_gpadc_iio *info = dev_id;
+
+	if (atomic_read(&info->ignore_temp_data_irq))
+		return IRQ_HANDLED;
+
+	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
+		complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
+{
+	struct sun4i_gpadc_iio *info = dev_id;
+
+	if (atomic_read(&info->ignore_fifo_data_irq))
+		return IRQ_HANDLED;
+
+	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
+		complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int sun4i_gpadc_runtime_suspend(struct device *dev)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
+
+	/* Disable the ADC on IP */
+	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
+	/* Disable temperature sensor on IP */
+	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
+
+	return 0;
+}
+
+static int sun4i_gpadc_runtime_resume(struct device *dev)
+{
+	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
+
+	/* clkin = 6MHz */
+	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
+		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
+		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
+		     SUN4I_GPADC_CTRL0_T_ACQ(63));
+	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
+	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
+		     SUN4I_GPADC_CTRL3_FILTER_EN |
+		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
+	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~1.3s */
+	regmap_write(info->regmap, SUN4I_GPADC_TPR,
+		     SUN4I_GPADC_TPR_TEMP_ENABLE |
+		     SUN4I_GPADC_TPR_TEMP_PERIOD(1953));
+
+	return 0;
+}
+
+static int sun4i_gpadc_get_temp(void *data, int *temp)
+{
+	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
+	int val, scale, offset;
+
+	/* If reading temperature times out, take stored previous value. */
+	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
+		val = info->temp_data;
+	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
+	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
+
+	*temp = (val + offset) * scale;
+
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
+	.get_temp = &sun4i_gpadc_get_temp,
+};
+
+static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
+	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
+	.runtime_resume = &sun4i_gpadc_runtime_resume,
+};
+
+static int sun4i_irq_init(struct platform_device *pdev, const char *name,
+			  irq_handler_t handler, const char *devname,
+			  unsigned int *irq, atomic_t *atomic)
+{
+	int ret;
+	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
+	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
+
+	/*
+	 * Once the interrupt is activated, the IP continuously performs
+	 * conversions thus throws interrupts. The interrupt is activated right
+	 * after being requested but we want to control when these interrupts
+	 * occur thus we disable it right after being requested. However, an
+	 * interrupt might occur between these two instructions and we have to
+	 * make sure that does not happen, by using atomic flags. We set the
+	 * flag before requesting the interrupt and unset it right after
+	 * disabling the interrupt. When an interrupt occurs between these two
+	 * instructions, reading the atomic flag will tell us to ignore the
+	 * interrupt.
+	 */
+	atomic_set(atomic, 1);
+
+	*irq = platform_get_irq_byname(pdev, name);
+	if (*irq < 0) {
+		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
+		return *irq;
+	}
+
+	*irq = regmap_irq_get_virq(mfd_dev->regmap_irqc, *irq);
+	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
+					   devname, info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
+			name, ret);
+		return ret;
+	}
+
+	disable_irq(*irq);
+	atomic_set(atomic, 0);
+
+	return 0;
+}
+
+static int sun4i_gpadc_probe(struct platform_device *pdev)
+{
+	struct sun4i_gpadc_iio *info;
+	struct iio_dev *indio_dev;
+	int ret;
+	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
+	struct thermal_zone_device *tzd;
+
+	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	info = iio_priv(indio_dev);
+	platform_set_drvdata(pdev, indio_dev);
+
+	mutex_init(&info->mutex);
+	info->regmap = sun4i_gpadc_dev->regmap;
+	info->indio_dev = indio_dev;
+	info->temp_data = -1;
+	info->adc_data = -1;
+	init_completion(&info->completion);
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->info = &sun4i_gpadc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
+	indio_dev->channels = sun4i_gpadc_channels;
+
+	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
+
+	/*
+	 * This driver is a child of an MFD which has a node in the DT but not
+	 * its children. Therefore, the resulting devices of this driver do not
+	 * have an of_node variable.
+	 * However, its parent (the MFD driver) has an of_node variable and
+	 * since devm_thermal_zone_of_sensor_register uses its first argument to
+	 * match the phandle defined in the node of the thermal driver with the
+	 * of_node of the device passed as first argument and the third argument
+	 * to call ops from thermal_zone_of_device_ops, the solution is to use
+	 * the parent device as first argument to match the phandle with its
+	 * of_node, and the device from this driver as third argument to return
+	 * the temperature.
+	 */
+	tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
+						   &sun4i_ts_tz_ops);
+	if (IS_ERR(tzd)) {
+		dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
+			PTR_ERR(tzd));
+		return PTR_ERR(tzd);
+	}
+
+	pm_runtime_set_autosuspend_delay(&pdev->dev,
+					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
+	pm_runtime_use_autosuspend(&pdev->dev);
+	pm_runtime_set_suspended(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
+			     sun4i_gpadc_temp_data_irq_handler, "temp_data",
+			     &info->temp_data_irq, &info->ignore_temp_data_irq);
+	if (ret < 0)
+		goto err;
+
+	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
+			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
+			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
+	if (ret < 0)
+		goto err;
+
+	ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to register iio map array\n");
+		goto err;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "could not register the device\n");
+		goto err_map;
+	}
+
+	return 0;
+
+err_map:
+	iio_map_array_unregister(indio_dev);
+
+err:
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	return ret;
+}
+
+static int sun4i_gpadc_remove(struct platform_device *pdev)
+{
+	struct sun4i_gpadc_iio *info;
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	info = iio_priv(indio_dev);
+	iio_map_array_unregister(indio_dev);
+	iio_device_unregister(indio_dev);
+
+	return 0;
+}
+
+static const struct platform_device_id sun4i_gpadc_id[] = {
+	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
+	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
+	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
+	{ /* sentinel */ },
+};
+
+static struct platform_driver sun4i_gpadc_driver = {
+	.driver = {
+		.name = "sun4i-gpadc-iio",
+		.pm = &sun4i_gpadc_pm_ops,
+	},
+	.id_table = sun4i_gpadc_id,
+	.probe = sun4i_gpadc_probe,
+	.remove = sun4i_gpadc_remove,
+};
+
+module_platform_driver(sun4i_gpadc_driver);
+
+MODULE_DESCRIPTION("ADC driver for sunxi platforms");
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v6 1/2] mfd: add support for Allwinner SoCs ADC
  2016-09-15 12:44   ` Quentin Schulz
@ 2016-10-26  9:18     ` Lee Jones
  -1 siblings, 0 replies; 14+ messages in thread
From: Lee Jones @ 2016-10-26  9:18 UTC (permalink / raw)
  To: Quentin Schulz
  Cc: jdelvare, linux, jic23, knaack.h, lars, pmeerw, maxime.ripard,
	wens, thomas.petazzoni, antoine.tenart, linux-kernel,
	linux-hwmon, linux-iio, linux-arm-kernel

On Thu, 15 Sep 2016, Quentin Schulz wrote:

> The Allwinner SoCs all have an ADC that can also act as a touchscreen
> controller and a thermal sensor. For now, only the ADC and the thermal
> sensor drivers are probed by the MFD, the touchscreen controller support
> will be added later.
> 
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> Acked-by: Jonathan Cameron <jic23@kernel.org>
> ---
> 
> v6:
>  - remove "-mfd" from filenames and variables inside MFD driver,
>  - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
>  - cosmetic changes,
>  - use IDs and switch over ID to get cells specific to an architecture, instead
>  of using cells direclty, in of_device_id.data,
>  - compute size of mfd_cells array instead of hardcoded one,
> 
> v5:
>  - correct mail address,
> 
> v4:
>  - rename files and variables from sunxi* to sun4i*,
>  - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
>  - remove TP in defines name,
>  - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
>  - use devm functions for regmap_add_irq_chip and mfd_add_devices,
>  - remove remove functions (now empty thanks to devm functions),
> 
> v3:
>  - use defines in regmap_irq instead of hard coded BITs,
>  - use of_device_id data field to chose which MFD cells to add considering
>    the compatible responsible of the MFD probe,
>  - remove useless initializations,
>  - disable all interrupts before adding them to regmap_irqchip,
>  - add goto error label in probe,
>  - correct wrapping in header license,
>  - move defines from IIO driver to header,
>  - use GENMASK to limit the size of the variable passed to a macro,
>  - prefix register BIT defines with the name of the register,
>  - reorder defines,
> 
> v2:
>  - add license headers,
>  - reorder alphabetically includes,
>  - add SUNXI_GPADC_ prefixes for defines,
> 
>  drivers/mfd/Kconfig             |  15 ++++
>  drivers/mfd/Makefile            |   2 +
>  drivers/mfd/sun4i-gpadc.c       | 181 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/sun4i-gpadc.h |  94 +++++++++++++++++++++
>  4 files changed, 292 insertions(+)
>  create mode 100644 drivers/mfd/sun4i-gpadc.c
>  create mode 100644 include/linux/mfd/sun4i-gpadc.h

Applied, thanks.

> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 1bcf601..45db3f4 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -29,6 +29,21 @@ config MFD_ACT8945A
>  	  linear regulators, along with a complete ActivePath battery
>  	  charger.
>  
> +config MFD_SUN4I_GPADC
> +	tristate "Allwinner sunxi platforms' GPADC MFD driver"
> +	select MFD_CORE
> +	select REGMAP_MMIO
> +	depends on ARCH_SUNXI || COMPILE_TEST
> +	help
> +	  Select this to get support for Allwinner SoCs (A10, A13 and A31) ADC.
> +	  This driver will only map the hardware interrupt and registers, you
> +	  have to select individual drivers based on this MFD to be able to use
> +	  the ADC or the thermal sensor. This will try to probe the ADC driver
> +	  sun4i-gpadc-iio and the hwmon driver iio_hwmon.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called sun4i-gpadc.
> +
>  config MFD_AS3711
>  	bool "AMS AS3711"
>  	select MFD_CORE
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 42a66e1..12c49af 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -205,3 +205,5 @@ intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
>  intel-soc-pmic-$(CONFIG_INTEL_PMC_IPC)	+= intel_soc_pmic_bxtwc.o
>  obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
>  obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o
> +
> +obj-$(CONFIG_MFD_SUN4I_GPADC)	+= sun4i-gpadc.o
> diff --git a/drivers/mfd/sun4i-gpadc.c b/drivers/mfd/sun4i-gpadc.c
> new file mode 100644
> index 0000000..276a1f4
> --- /dev/null
> +++ b/drivers/mfd/sun4i-gpadc.c
> @@ -0,0 +1,181 @@
> +/* ADC MFD core driver for sunxi platforms
> + *
> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by
> + * the Free Software Foundation.
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/of_irq.h>
> +#include <linux/regmap.h>
> +
> +#include <linux/mfd/sun4i-gpadc.h>
> +
> +#define ARCH_SUN4I_A10 0
> +#define ARCH_SUN5I_A13 1
> +#define ARCH_SUN6I_A31 2
> +
> +static struct resource adc_resources[] = {
> +	DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_FIFO_DATA, "FIFO_DATA_PENDING"),
> +	DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_TEMP_DATA, "TEMP_DATA_PENDING"),
> +};
> +
> +static const struct regmap_irq sun4i_gpadc_regmap_irq[] = {
> +	REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_FIFO_DATA, 0,
> +		       SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN),
> +	REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_TEMP_DATA, 0,
> +		       SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN),
> +};
> +
> +static const struct regmap_irq_chip sun4i_gpadc_regmap_irq_chip = {
> +	.name = "sun4i_gpadc_irq_chip",
> +	.status_base = SUN4I_GPADC_INT_FIFOS,
> +	.ack_base = SUN4I_GPADC_INT_FIFOS,
> +	.mask_base = SUN4I_GPADC_INT_FIFOC,
> +	.init_ack_masked = true,
> +	.mask_invert = true,
> +	.irqs = sun4i_gpadc_regmap_irq,
> +	.num_irqs = ARRAY_SIZE(sun4i_gpadc_regmap_irq),
> +	.num_regs = 1,
> +};
> +
> +static struct mfd_cell sun4i_gpadc_cells[] = {
> +	{
> +		.name	= "sun4i-a10-gpadc-iio",
> +		.resources = adc_resources,
> +		.num_resources = ARRAY_SIZE(adc_resources),
> +	},
> +	{ .name = "iio_hwmon" }
> +};
> +
> +static struct mfd_cell sun5i_gpadc_cells[] = {
> +	{
> +		.name	= "sun5i-a13-gpadc-iio",
> +		.resources = adc_resources,
> +		.num_resources = ARRAY_SIZE(adc_resources),
> +	},
> +	{ .name = "iio_hwmon" },
> +};
> +
> +static struct mfd_cell sun6i_gpadc_cells[] = {
> +	{
> +		.name	= "sun6i-a31-gpadc-iio",
> +		.resources = adc_resources,
> +		.num_resources = ARRAY_SIZE(adc_resources),
> +	},
> +	{ .name = "iio_hwmon" },
> +};
> +
> +static const struct regmap_config sun4i_gpadc_regmap_config = {
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 4,
> +	.fast_io = true,
> +};
> +
> +static const struct of_device_id sun4i_gpadc_of_match[] = {
> +	{
> +		.compatible = "allwinner,sun4i-a10-ts",
> +		.data = (void *)ARCH_SUN4I_A10,
> +	}, {
> +		.compatible = "allwinner,sun5i-a13-ts",
> +		.data = (void *)ARCH_SUN5I_A13,
> +	}, {
> +		.compatible = "allwinner,sun6i-a31-ts",
> +		.data = (void *)ARCH_SUN6I_A31,
> +	}, { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, sun4i_gpadc_of_match);
> +
> +static int sun4i_gpadc_probe(struct platform_device *pdev)
> +{
> +	struct sun4i_gpadc_dev *dev;
> +	struct resource *mem;
> +	const struct of_device_id *of_id;
> +	const struct mfd_cell *cells;
> +	unsigned int irq, size;
> +	int ret;
> +
> +	of_id = of_match_node(sun4i_gpadc_of_match, pdev->dev.of_node);
> +	if (!of_id)
> +		return -EINVAL;
> +
> +	switch ((int)of_id->data) {
> +	case ARCH_SUN4I_A10:
> +		cells = sun4i_gpadc_cells;
> +		size = ARRAY_SIZE(sun4i_gpadc_cells);
> +		break;
> +	case ARCH_SUN5I_A13:
> +		cells = sun5i_gpadc_cells;
> +		size = ARRAY_SIZE(sun5i_gpadc_cells);
> +		break;
> +	case ARCH_SUN6I_A31:
> +		cells = sun6i_gpadc_cells;
> +		size = ARRAY_SIZE(sun6i_gpadc_cells);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
> +	if (!dev)
> +		return -ENOMEM;
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	dev->base = devm_ioremap_resource(&pdev->dev, mem);
> +	if (IS_ERR(dev->base))
> +		return PTR_ERR(dev->base);
> +
> +	dev->dev = &pdev->dev;
> +	dev_set_drvdata(dev->dev, dev);
> +
> +	dev->regmap = devm_regmap_init_mmio(dev->dev, dev->base,
> +					    &sun4i_gpadc_regmap_config);
> +	if (IS_ERR(dev->regmap)) {
> +		ret = PTR_ERR(dev->regmap);
> +		dev_err(&pdev->dev, "failed to init regmap: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Disable all interrupts */
> +	regmap_write(dev->regmap, SUN4I_GPADC_INT_FIFOC, 0);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	ret = devm_regmap_add_irq_chip(&pdev->dev, dev->regmap, irq,
> +				       IRQF_ONESHOT, 0,
> +				       &sun4i_gpadc_regmap_irq_chip,
> +				       &dev->regmap_irqc);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to add irq chip: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = devm_mfd_add_devices(dev->dev, 0, cells, size, NULL, 0, NULL);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to add MFD devices: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct platform_driver sun4i_gpadc_driver = {
> +	.driver = {
> +		.name = "sun4i-gpadc",
> +		.of_match_table = of_match_ptr(sun4i_gpadc_of_match),
> +	},
> +	.probe = sun4i_gpadc_probe,
> +};
> +
> +module_platform_driver(sun4i_gpadc_driver);
> +
> +MODULE_DESCRIPTION("Allwinner sunxi platforms' GPADC MFD core driver");
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
> new file mode 100644
> index 0000000..d7a29f2
> --- /dev/null
> +++ b/include/linux/mfd/sun4i-gpadc.h
> @@ -0,0 +1,94 @@
> +/* Header of ADC MFD core driver for sunxi platforms
> + *
> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
> + * Free Software Foundation.
> + */
> +
> +#ifndef __SUN4I_GPADC__H__
> +#define __SUN4I_GPADC__H__
> +
> +#define SUN4I_GPADC_CTRL0				0x00
> +
> +#define SUN4I_GPADC_CTRL0_ADC_FIRST_DLY(x)		((GENMASK(7, 0) & (x)) << 24)
> +#define SUN4I_GPADC_CTRL0_ADC_FIRST_DLY_MODE		BIT(23)
> +#define SUN4I_GPADC_CTRL0_ADC_CLK_SELECT		BIT(22)
> +#define SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(x)		((GENMASK(1, 0) & (x)) << 20)
> +#define SUN4I_GPADC_CTRL0_FS_DIV(x)			((GENMASK(3, 0) & (x)) << 16)
> +#define SUN4I_GPADC_CTRL0_T_ACQ(x)			(GENMASK(15, 0) & (x))
> +
> +#define SUN4I_GPADC_CTRL1				0x04
> +
> +#define SUN4I_GPADC_CTRL1_STYLUS_UP_DEBOUNCE(x)		((GENMASK(7, 0) & (x)) << 12)
> +#define SUN4I_GPADC_CTRL1_STYLUS_UP_DEBOUNCE_EN		BIT(9)
> +#define SUN4I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(6)
> +#define SUN4I_GPADC_CTRL1_TP_DUAL_EN			BIT(5)
> +#define SUN4I_GPADC_CTRL1_TP_MODE_EN			BIT(4)
> +#define SUN4I_GPADC_CTRL1_TP_ADC_SELECT			BIT(3)
> +#define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(2, 0) & (x))
> +
> +/* TP_CTRL1 bits for sun6i SOCs */
> +#define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(7)
> +#define SUN6I_GPADC_CTRL1_TP_DUAL_EN			BIT(6)
> +#define SUN6I_GPADC_CTRL1_TP_MODE_EN			BIT(5)
> +#define SUN6I_GPADC_CTRL1_TP_ADC_SELECT			BIT(4)
> +#define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(3, 0) & BIT(x))
> +
> +#define SUN4I_GPADC_CTRL2				0x08
> +
> +#define SUN4I_GPADC_CTRL2_TP_SENSITIVE_ADJUST(x)	((GENMASK(3, 0) & (x)) << 28)
> +#define SUN4I_GPADC_CTRL2_TP_MODE_SELECT(x)		((GENMASK(1, 0) & (x)) << 26)
> +#define SUN4I_GPADC_CTRL2_PRE_MEA_EN			BIT(24)
> +#define SUN4I_GPADC_CTRL2_PRE_MEA_THRE_CNT(x)		(GENMASK(23, 0) & (x))
> +
> +#define SUN4I_GPADC_CTRL3				0x0c
> +
> +#define SUN4I_GPADC_CTRL3_FILTER_EN			BIT(2)
> +#define SUN4I_GPADC_CTRL3_FILTER_TYPE(x)		(GENMASK(1, 0) & (x))
> +
> +#define SUN4I_GPADC_TPR					0x18
> +
> +#define SUN4I_GPADC_TPR_TEMP_ENABLE			BIT(16)
> +#define SUN4I_GPADC_TPR_TEMP_PERIOD(x)			(GENMASK(15, 0) & (x))
> +
> +#define SUN4I_GPADC_INT_FIFOC				0x10
> +
> +#define SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN		BIT(18)
> +#define SUN4I_GPADC_INT_FIFOC_TP_OVERRUN_IRQ_EN		BIT(17)
> +#define SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN		BIT(16)
> +#define SUN4I_GPADC_INT_FIFOC_TP_DATA_XY_CHANGE		BIT(13)
> +#define SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(x)	((GENMASK(4, 0) & (x)) << 8)
> +#define SUN4I_GPADC_INT_FIFOC_TP_DATA_DRQ_EN		BIT(7)
> +#define SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH		BIT(4)
> +#define SUN4I_GPADC_INT_FIFOC_TP_UP_IRQ_EN		BIT(1)
> +#define SUN4I_GPADC_INT_FIFOC_TP_DOWN_IRQ_EN		BIT(0)
> +
> +#define SUN4I_GPADC_INT_FIFOS				0x14
> +
> +#define SUN4I_GPADC_INT_FIFOS_TEMP_DATA_PENDING		BIT(18)
> +#define SUN4I_GPADC_INT_FIFOS_FIFO_OVERRUN_PENDING	BIT(17)
> +#define SUN4I_GPADC_INT_FIFOS_FIFO_DATA_PENDING		BIT(16)
> +#define SUN4I_GPADC_INT_FIFOS_TP_IDLE_FLG		BIT(2)
> +#define SUN4I_GPADC_INT_FIFOS_TP_UP_PENDING		BIT(1)
> +#define SUN4I_GPADC_INT_FIFOS_TP_DOWN_PENDING		BIT(0)
> +
> +#define SUN4I_GPADC_CDAT				0x1c
> +#define SUN4I_GPADC_TEMP_DATA				0x20
> +#define SUN4I_GPADC_DATA				0x24
> +
> +#define SUN4I_GPADC_IRQ_FIFO_DATA			0
> +#define SUN4I_GPADC_IRQ_TEMP_DATA			1
> +
> +/* 10s delay before suspending the IP */
> +#define SUN4I_GPADC_AUTOSUSPEND_DELAY			10000
> +
> +struct sun4i_gpadc_dev {
> +	struct device			*dev;
> +	struct regmap			*regmap;
> +	struct regmap_irq_chip_data	*regmap_irqc;
> +	void __iomem			*base;
> +};
> +
> +#endif

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v6 1/2] mfd: add support for Allwinner SoCs ADC
@ 2016-10-26  9:18     ` Lee Jones
  0 siblings, 0 replies; 14+ messages in thread
From: Lee Jones @ 2016-10-26  9:18 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, 15 Sep 2016, Quentin Schulz wrote:

> The Allwinner SoCs all have an ADC that can also act as a touchscreen
> controller and a thermal sensor. For now, only the ADC and the thermal
> sensor drivers are probed by the MFD, the touchscreen controller support
> will be added later.
> 
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> Acked-by: Jonathan Cameron <jic23@kernel.org>
> ---
> 
> v6:
>  - remove "-mfd" from filenames and variables inside MFD driver,
>  - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
>  - cosmetic changes,
>  - use IDs and switch over ID to get cells specific to an architecture, instead
>  of using cells direclty, in of_device_id.data,
>  - compute size of mfd_cells array instead of hardcoded one,
> 
> v5:
>  - correct mail address,
> 
> v4:
>  - rename files and variables from sunxi* to sun4i*,
>  - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
>  - remove TP in defines name,
>  - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
>  - use devm functions for regmap_add_irq_chip and mfd_add_devices,
>  - remove remove functions (now empty thanks to devm functions),
> 
> v3:
>  - use defines in regmap_irq instead of hard coded BITs,
>  - use of_device_id data field to chose which MFD cells to add considering
>    the compatible responsible of the MFD probe,
>  - remove useless initializations,
>  - disable all interrupts before adding them to regmap_irqchip,
>  - add goto error label in probe,
>  - correct wrapping in header license,
>  - move defines from IIO driver to header,
>  - use GENMASK to limit the size of the variable passed to a macro,
>  - prefix register BIT defines with the name of the register,
>  - reorder defines,
> 
> v2:
>  - add license headers,
>  - reorder alphabetically includes,
>  - add SUNXI_GPADC_ prefixes for defines,
> 
>  drivers/mfd/Kconfig             |  15 ++++
>  drivers/mfd/Makefile            |   2 +
>  drivers/mfd/sun4i-gpadc.c       | 181 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/sun4i-gpadc.h |  94 +++++++++++++++++++++
>  4 files changed, 292 insertions(+)
>  create mode 100644 drivers/mfd/sun4i-gpadc.c
>  create mode 100644 include/linux/mfd/sun4i-gpadc.h

Applied, thanks.

> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 1bcf601..45db3f4 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -29,6 +29,21 @@ config MFD_ACT8945A
>  	  linear regulators, along with a complete ActivePath battery
>  	  charger.
>  
> +config MFD_SUN4I_GPADC
> +	tristate "Allwinner sunxi platforms' GPADC MFD driver"
> +	select MFD_CORE
> +	select REGMAP_MMIO
> +	depends on ARCH_SUNXI || COMPILE_TEST
> +	help
> +	  Select this to get support for Allwinner SoCs (A10, A13 and A31) ADC.
> +	  This driver will only map the hardware interrupt and registers, you
> +	  have to select individual drivers based on this MFD to be able to use
> +	  the ADC or the thermal sensor. This will try to probe the ADC driver
> +	  sun4i-gpadc-iio and the hwmon driver iio_hwmon.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called sun4i-gpadc.
> +
>  config MFD_AS3711
>  	bool "AMS AS3711"
>  	select MFD_CORE
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 42a66e1..12c49af 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -205,3 +205,5 @@ intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
>  intel-soc-pmic-$(CONFIG_INTEL_PMC_IPC)	+= intel_soc_pmic_bxtwc.o
>  obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
>  obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o
> +
> +obj-$(CONFIG_MFD_SUN4I_GPADC)	+= sun4i-gpadc.o
> diff --git a/drivers/mfd/sun4i-gpadc.c b/drivers/mfd/sun4i-gpadc.c
> new file mode 100644
> index 0000000..276a1f4
> --- /dev/null
> +++ b/drivers/mfd/sun4i-gpadc.c
> @@ -0,0 +1,181 @@
> +/* ADC MFD core driver for sunxi platforms
> + *
> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by
> + * the Free Software Foundation.
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/of_irq.h>
> +#include <linux/regmap.h>
> +
> +#include <linux/mfd/sun4i-gpadc.h>
> +
> +#define ARCH_SUN4I_A10 0
> +#define ARCH_SUN5I_A13 1
> +#define ARCH_SUN6I_A31 2
> +
> +static struct resource adc_resources[] = {
> +	DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_FIFO_DATA, "FIFO_DATA_PENDING"),
> +	DEFINE_RES_IRQ_NAMED(SUN4I_GPADC_IRQ_TEMP_DATA, "TEMP_DATA_PENDING"),
> +};
> +
> +static const struct regmap_irq sun4i_gpadc_regmap_irq[] = {
> +	REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_FIFO_DATA, 0,
> +		       SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN),
> +	REGMAP_IRQ_REG(SUN4I_GPADC_IRQ_TEMP_DATA, 0,
> +		       SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN),
> +};
> +
> +static const struct regmap_irq_chip sun4i_gpadc_regmap_irq_chip = {
> +	.name = "sun4i_gpadc_irq_chip",
> +	.status_base = SUN4I_GPADC_INT_FIFOS,
> +	.ack_base = SUN4I_GPADC_INT_FIFOS,
> +	.mask_base = SUN4I_GPADC_INT_FIFOC,
> +	.init_ack_masked = true,
> +	.mask_invert = true,
> +	.irqs = sun4i_gpadc_regmap_irq,
> +	.num_irqs = ARRAY_SIZE(sun4i_gpadc_regmap_irq),
> +	.num_regs = 1,
> +};
> +
> +static struct mfd_cell sun4i_gpadc_cells[] = {
> +	{
> +		.name	= "sun4i-a10-gpadc-iio",
> +		.resources = adc_resources,
> +		.num_resources = ARRAY_SIZE(adc_resources),
> +	},
> +	{ .name = "iio_hwmon" }
> +};
> +
> +static struct mfd_cell sun5i_gpadc_cells[] = {
> +	{
> +		.name	= "sun5i-a13-gpadc-iio",
> +		.resources = adc_resources,
> +		.num_resources = ARRAY_SIZE(adc_resources),
> +	},
> +	{ .name = "iio_hwmon" },
> +};
> +
> +static struct mfd_cell sun6i_gpadc_cells[] = {
> +	{
> +		.name	= "sun6i-a31-gpadc-iio",
> +		.resources = adc_resources,
> +		.num_resources = ARRAY_SIZE(adc_resources),
> +	},
> +	{ .name = "iio_hwmon" },
> +};
> +
> +static const struct regmap_config sun4i_gpadc_regmap_config = {
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 4,
> +	.fast_io = true,
> +};
> +
> +static const struct of_device_id sun4i_gpadc_of_match[] = {
> +	{
> +		.compatible = "allwinner,sun4i-a10-ts",
> +		.data = (void *)ARCH_SUN4I_A10,
> +	}, {
> +		.compatible = "allwinner,sun5i-a13-ts",
> +		.data = (void *)ARCH_SUN5I_A13,
> +	}, {
> +		.compatible = "allwinner,sun6i-a31-ts",
> +		.data = (void *)ARCH_SUN6I_A31,
> +	}, { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, sun4i_gpadc_of_match);
> +
> +static int sun4i_gpadc_probe(struct platform_device *pdev)
> +{
> +	struct sun4i_gpadc_dev *dev;
> +	struct resource *mem;
> +	const struct of_device_id *of_id;
> +	const struct mfd_cell *cells;
> +	unsigned int irq, size;
> +	int ret;
> +
> +	of_id = of_match_node(sun4i_gpadc_of_match, pdev->dev.of_node);
> +	if (!of_id)
> +		return -EINVAL;
> +
> +	switch ((int)of_id->data) {
> +	case ARCH_SUN4I_A10:
> +		cells = sun4i_gpadc_cells;
> +		size = ARRAY_SIZE(sun4i_gpadc_cells);
> +		break;
> +	case ARCH_SUN5I_A13:
> +		cells = sun5i_gpadc_cells;
> +		size = ARRAY_SIZE(sun5i_gpadc_cells);
> +		break;
> +	case ARCH_SUN6I_A31:
> +		cells = sun6i_gpadc_cells;
> +		size = ARRAY_SIZE(sun6i_gpadc_cells);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
> +	if (!dev)
> +		return -ENOMEM;
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	dev->base = devm_ioremap_resource(&pdev->dev, mem);
> +	if (IS_ERR(dev->base))
> +		return PTR_ERR(dev->base);
> +
> +	dev->dev = &pdev->dev;
> +	dev_set_drvdata(dev->dev, dev);
> +
> +	dev->regmap = devm_regmap_init_mmio(dev->dev, dev->base,
> +					    &sun4i_gpadc_regmap_config);
> +	if (IS_ERR(dev->regmap)) {
> +		ret = PTR_ERR(dev->regmap);
> +		dev_err(&pdev->dev, "failed to init regmap: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Disable all interrupts */
> +	regmap_write(dev->regmap, SUN4I_GPADC_INT_FIFOC, 0);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	ret = devm_regmap_add_irq_chip(&pdev->dev, dev->regmap, irq,
> +				       IRQF_ONESHOT, 0,
> +				       &sun4i_gpadc_regmap_irq_chip,
> +				       &dev->regmap_irqc);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to add irq chip: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = devm_mfd_add_devices(dev->dev, 0, cells, size, NULL, 0, NULL);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to add MFD devices: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct platform_driver sun4i_gpadc_driver = {
> +	.driver = {
> +		.name = "sun4i-gpadc",
> +		.of_match_table = of_match_ptr(sun4i_gpadc_of_match),
> +	},
> +	.probe = sun4i_gpadc_probe,
> +};
> +
> +module_platform_driver(sun4i_gpadc_driver);
> +
> +MODULE_DESCRIPTION("Allwinner sunxi platforms' GPADC MFD core driver");
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
> new file mode 100644
> index 0000000..d7a29f2
> --- /dev/null
> +++ b/include/linux/mfd/sun4i-gpadc.h
> @@ -0,0 +1,94 @@
> +/* Header of ADC MFD core driver for sunxi platforms
> + *
> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
> + * Free Software Foundation.
> + */
> +
> +#ifndef __SUN4I_GPADC__H__
> +#define __SUN4I_GPADC__H__
> +
> +#define SUN4I_GPADC_CTRL0				0x00
> +
> +#define SUN4I_GPADC_CTRL0_ADC_FIRST_DLY(x)		((GENMASK(7, 0) & (x)) << 24)
> +#define SUN4I_GPADC_CTRL0_ADC_FIRST_DLY_MODE		BIT(23)
> +#define SUN4I_GPADC_CTRL0_ADC_CLK_SELECT		BIT(22)
> +#define SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(x)		((GENMASK(1, 0) & (x)) << 20)
> +#define SUN4I_GPADC_CTRL0_FS_DIV(x)			((GENMASK(3, 0) & (x)) << 16)
> +#define SUN4I_GPADC_CTRL0_T_ACQ(x)			(GENMASK(15, 0) & (x))
> +
> +#define SUN4I_GPADC_CTRL1				0x04
> +
> +#define SUN4I_GPADC_CTRL1_STYLUS_UP_DEBOUNCE(x)		((GENMASK(7, 0) & (x)) << 12)
> +#define SUN4I_GPADC_CTRL1_STYLUS_UP_DEBOUNCE_EN		BIT(9)
> +#define SUN4I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(6)
> +#define SUN4I_GPADC_CTRL1_TP_DUAL_EN			BIT(5)
> +#define SUN4I_GPADC_CTRL1_TP_MODE_EN			BIT(4)
> +#define SUN4I_GPADC_CTRL1_TP_ADC_SELECT			BIT(3)
> +#define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(2, 0) & (x))
> +
> +/* TP_CTRL1 bits for sun6i SOCs */
> +#define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(7)
> +#define SUN6I_GPADC_CTRL1_TP_DUAL_EN			BIT(6)
> +#define SUN6I_GPADC_CTRL1_TP_MODE_EN			BIT(5)
> +#define SUN6I_GPADC_CTRL1_TP_ADC_SELECT			BIT(4)
> +#define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(3, 0) & BIT(x))
> +
> +#define SUN4I_GPADC_CTRL2				0x08
> +
> +#define SUN4I_GPADC_CTRL2_TP_SENSITIVE_ADJUST(x)	((GENMASK(3, 0) & (x)) << 28)
> +#define SUN4I_GPADC_CTRL2_TP_MODE_SELECT(x)		((GENMASK(1, 0) & (x)) << 26)
> +#define SUN4I_GPADC_CTRL2_PRE_MEA_EN			BIT(24)
> +#define SUN4I_GPADC_CTRL2_PRE_MEA_THRE_CNT(x)		(GENMASK(23, 0) & (x))
> +
> +#define SUN4I_GPADC_CTRL3				0x0c
> +
> +#define SUN4I_GPADC_CTRL3_FILTER_EN			BIT(2)
> +#define SUN4I_GPADC_CTRL3_FILTER_TYPE(x)		(GENMASK(1, 0) & (x))
> +
> +#define SUN4I_GPADC_TPR					0x18
> +
> +#define SUN4I_GPADC_TPR_TEMP_ENABLE			BIT(16)
> +#define SUN4I_GPADC_TPR_TEMP_PERIOD(x)			(GENMASK(15, 0) & (x))
> +
> +#define SUN4I_GPADC_INT_FIFOC				0x10
> +
> +#define SUN4I_GPADC_INT_FIFOC_TEMP_IRQ_EN		BIT(18)
> +#define SUN4I_GPADC_INT_FIFOC_TP_OVERRUN_IRQ_EN		BIT(17)
> +#define SUN4I_GPADC_INT_FIFOC_TP_DATA_IRQ_EN		BIT(16)
> +#define SUN4I_GPADC_INT_FIFOC_TP_DATA_XY_CHANGE		BIT(13)
> +#define SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(x)	((GENMASK(4, 0) & (x)) << 8)
> +#define SUN4I_GPADC_INT_FIFOC_TP_DATA_DRQ_EN		BIT(7)
> +#define SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH		BIT(4)
> +#define SUN4I_GPADC_INT_FIFOC_TP_UP_IRQ_EN		BIT(1)
> +#define SUN4I_GPADC_INT_FIFOC_TP_DOWN_IRQ_EN		BIT(0)
> +
> +#define SUN4I_GPADC_INT_FIFOS				0x14
> +
> +#define SUN4I_GPADC_INT_FIFOS_TEMP_DATA_PENDING		BIT(18)
> +#define SUN4I_GPADC_INT_FIFOS_FIFO_OVERRUN_PENDING	BIT(17)
> +#define SUN4I_GPADC_INT_FIFOS_FIFO_DATA_PENDING		BIT(16)
> +#define SUN4I_GPADC_INT_FIFOS_TP_IDLE_FLG		BIT(2)
> +#define SUN4I_GPADC_INT_FIFOS_TP_UP_PENDING		BIT(1)
> +#define SUN4I_GPADC_INT_FIFOS_TP_DOWN_PENDING		BIT(0)
> +
> +#define SUN4I_GPADC_CDAT				0x1c
> +#define SUN4I_GPADC_TEMP_DATA				0x20
> +#define SUN4I_GPADC_DATA				0x24
> +
> +#define SUN4I_GPADC_IRQ_FIFO_DATA			0
> +#define SUN4I_GPADC_IRQ_TEMP_DATA			1
> +
> +/* 10s delay before suspending the IP */
> +#define SUN4I_GPADC_AUTOSUSPEND_DELAY			10000
> +
> +struct sun4i_gpadc_dev {
> +	struct device			*dev;
> +	struct regmap			*regmap;
> +	struct regmap_irq_chip_data	*regmap_irqc;
> +	void __iomem			*base;
> +};
> +
> +#endif

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org ? Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v6 2/2] iio: adc: add support for Allwinner SoCs ADC
  2016-09-15 12:44   ` Quentin Schulz
@ 2016-10-30 16:59     ` Jonathan Cameron
  -1 siblings, 0 replies; 14+ messages in thread
From: Jonathan Cameron @ 2016-10-30 16:59 UTC (permalink / raw)
  To: Quentin Schulz, jdelvare, linux, knaack.h, lars, pmeerw,
	maxime.ripard, wens, lee.jones
  Cc: thomas.petazzoni, antoine.tenart, linux-kernel, linux-hwmon,
	linux-iio, linux-arm-kernel

On 15/09/16 13:44, Quentin Schulz wrote:
> The Allwinner SoCs all have an ADC that can also act as a touchscreen
> controller and a thermal sensor. This patch adds the ADC driver which is
> based on the MFD for the same SoCs ADC.
> 
> This also registers the thermal adc channel in the iio map array so
> iio_hwmon could use it without modifying the Device Tree. This registers
> the driver in the thermal framework.
> 
> This driver probes on three different platform_device_id to take into
> account slight differences (registers bit and temperature computation)
> between Allwinner SoCs ADCs.
> 
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> Acked-by: Jonathan Cameron <jic23@kernel.org>
Hi Lee,

As you applied the MFD part of this series, could you pick this up as well?

Thanks,

Jonathan
> ---
> 
> v6:
>  - remove useless member (regs) from sun4i_gpadc_dev structure,
>  - rename sun4i_gpadc_dev structure to sun4i_gpadc_iio,
>  - remove regmap_update_bits used to disable hardware interrupts, it is already
>  handled by devm functions,
> 
> v5:
>  - correct mail address,
>  - correct several typos,
>  - move from const to static for sunxi_gpadc_chan_select functions,
>  - rename soc_specific struct to gpadc_data,
>  - rename soc_specific field to data in sun4i_gpadc_dev,
>  - return error code from regmap_write in case of failure in read_raws,
>  - share if condition in IIO_CHAN_INFO_RAW case,
>  - add comment on why we use parent device for registering in thermal,
>  - reordering remove function,
> 
> v4:
>  - rename files and variables from sunxi* to sun4i*,
>  - shorten sunxi_gpadc_soc_specific structure to soc_specific,
>  - factorize sysfs ADC and temp read_raws,
>  - use cached values when read_raw times out (except before a first value
>    is gotten),
>  - remove mutex locks and unlocks from runtime_pm functions,
>  - factorize irq initializations,
>  - initialize temp_data and fifo_data values to -1 (error value),
>  - "impersonate" MFD to register in thermal framework,
>  - deactivate hardware interrupts one by one when probe fails or when
>    removing driver instead of blindly deactivating all hardware interrupts,
>  - selects THERMAL_OF in Kconfig,
> 
> v3:
>  - correct wrapping,
>  - add comment about thermal sensor inner working,
>  - move defines in mfd header,
>  - use structure to define SoC specific registers or behaviour,
>  - attach this structure to the device according to of_device_id of the
>    platform device,
>  - use new mutex instead of iio_dev mutex,
>  - use atomic flags to avoid race between request_irq and disable_irq in
>    probe,
>  - switch from processed value to raw, offset and scale values for
>    temperature ADC channel,
>  - remove faulty sentinel in iio_chan_spec array,
>  - add pm_runtime support,
>  - register thermal sensor in thermal framework (forgotten since the
>    beginning whereas it is present in current sun4i-ts driver),
>  - remove useless ret variables to store return value of regmap_reads,
>  - move comments on thermal sensor acquisition period in code instead of
>    header,
>  - adding goto label to unregister iio_map_array when failing to register
>    iio_dev,
> 
> v2:
>  - add SUNXI_GPADC_ prefixes for defines,
>  - correct typo in Kconfig,
>  - reorder alphabetically includes, makefile,
>  - add license header,
>  - fix architecture variations not being handled in interrupt handlers or
>    read raw functions,
>  - fix unability to return negative values from thermal sensor,
>  - add gotos to reduce code repetition,
>  - fix irq variable being unsigned int instead of int,
>  - remove useless dev_err and dev_info,
>  - deactivate all interrupts if probe fails,
>  - fix iio_device_register on NULL variable,
>  - deactivate ADC in the IP when probe fails or when removing driver,
> 
>  drivers/iio/adc/Kconfig           |  13 +
>  drivers/iio/adc/Makefile          |   1 +
>  drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 536 insertions(+)
>  create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 25378c5..ea36a4f 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -384,6 +384,19 @@ config ROCKCHIP_SARADC
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called rockchip_saradc.
>  
> +config SUN4I_GPADC
> +	tristate "Support for the Allwinner SoCs GPADC"
> +	depends on IIO
> +	depends on MFD_SUN4I_GPADC
> +	select THERMAL_OF
> +	help
> +	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
> +	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
> +	  a touchscreen input and one channel for thermal sensor.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called sun4i-gpadc-iio.
> +
>  config TI_ADC081C
>  	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>  	depends on I2C
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 38638d4..204372d 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -37,6 +37,7 @@ 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
>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>  obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
> new file mode 100644
> index 0000000..e2c5ba8
> --- /dev/null
> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
> @@ -0,0 +1,522 @@
> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
> + *
> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
> + * Free Software Foundation.
> + *
> + * The Allwinner SoCs all have an ADC that can also act as a touchscreen
> + * controller and a thermal sensor.
> + * The thermal sensor works only when the ADC acts as a touchscreen controller
> + * and is configured to throw an interrupt every fixed periods of time (let say
> + * every X seconds).
> + * One would be tempted to disable the IP on the hardware side rather than
> + * disabling interrupts to save some power but that resets the internal clock of
> + * the IP, resulting in having to wait X seconds every time we want to read the
> + * value of the thermal sensor.
> + * This is also the reason of using autosuspend in pm_runtime. If there was no
> + * autosuspend, the thermal sensor would need X seconds after every
> + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
> + * thermal sensor to be requested again in a certain time span before it gets
> + * shutdown for not being used.
> + */
> +
> +#include <linux/completion.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/thermal.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/driver.h>
> +#include <linux/iio/machine.h>
> +#include <linux/mfd/sun4i-gpadc.h>
> +
> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
> +{
> +	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
> +}
> +
> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
> +{
> +	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
> +}
> +
> +struct gpadc_data {
> +	int		temp_offset;
> +	int		temp_scale;
> +	unsigned int	tp_mode_en;
> +	unsigned int	tp_adc_select;
> +	unsigned int	(*adc_chan_select)(unsigned int chan);
> +};
> +
> +static const struct gpadc_data sun4i_gpadc_data = {
> +	.temp_offset = -1932,
> +	.temp_scale = 133,
> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun4i_gpadc_chan_select,
> +};
> +
> +static const struct gpadc_data sun5i_gpadc_data = {
> +	.temp_offset = -1447,
> +	.temp_scale = 100,
> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun4i_gpadc_chan_select,
> +};
> +
> +static const struct gpadc_data sun6i_gpadc_data = {
> +	.temp_offset = -1623,
> +	.temp_scale = 167,
> +	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun6i_gpadc_chan_select,
> +};
> +
> +struct sun4i_gpadc_iio {
> +	struct iio_dev			*indio_dev;
> +	struct completion		completion;
> +	int				temp_data;
> +	u32				adc_data;
> +	struct regmap			*regmap;
> +	unsigned int			fifo_data_irq;
> +	atomic_t			ignore_fifo_data_irq;
> +	unsigned int			temp_data_irq;
> +	atomic_t			ignore_temp_data_irq;
> +	const struct gpadc_data		*data;
> +	/* prevents concurrent reads of temperature and ADC */
> +	struct mutex			mutex;
> +};
> +
> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
> +	.type = IIO_VOLTAGE,					\
> +	.indexed = 1,						\
> +	.channel = _channel,					\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
> +	.datasheet_name = _name,				\
> +}
> +
> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
> +	{
> +		.adc_channel_label = "temp_adc",
> +		.consumer_dev_name = "iio_hwmon.0",
> +	},
> +	{ /* sentinel */ },
> +};
> +
> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
> +	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
> +	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
> +	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
> +	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
> +	{
> +		.type = IIO_TEMP,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE) |
> +				      BIT(IIO_CHAN_INFO_OFFSET),
> +		.datasheet_name = "temp_adc",
> +	},
> +};
> +
> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
> +			    unsigned int irq)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +	int ret = 0;
> +
> +	pm_runtime_get_sync(indio_dev->dev.parent);
> +	mutex_lock(&info->mutex);
> +
> +	reinit_completion(&info->completion);
> +
> +	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
> +	if (ret)
> +		return ret;
> +
> +	if (irq == info->fifo_data_irq) {
> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
> +				   info->data->tp_mode_en |
> +				   info->data->tp_adc_select |
> +				   info->data->adc_chan_select(channel));
> +	} else {
> +		/*
> +		 * The temperature sensor returns valid data only when the ADC
> +		 * operates in touchscreen mode.
> +		 */
> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
> +				   info->data->tp_mode_en);
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	enable_irq(irq);
> +
> +	if (!wait_for_completion_timeout(&info->completion,
> +					 msecs_to_jiffies(100))) {
> +		if ((irq == info->fifo_data_irq && info->adc_data == -1) ||
> +		    (irq == info->temp_data_irq && info->temp_data == -1)) {
> +			ret = -ETIMEDOUT;
> +			goto out;
> +		}
> +	}
> +
> +	if (irq == info->fifo_data_irq)
> +		*val = info->adc_data;
> +	else
> +		*val = info->temp_data;
> +
> +	ret = 0;
> +
> +out:
> +	disable_irq(irq);
> +	mutex_unlock(&info->mutex);
> +	pm_runtime_mark_last_busy(indio_dev->dev.parent);
> +	pm_runtime_put_autosuspend(indio_dev->dev.parent);
> +
> +	return ret;
> +}
> +
> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
> +				int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
> +}
> +
> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
> +}
> +
> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	*val = info->data->temp_offset;
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	*val = info->data->temp_scale;
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan, int *val,
> +				int *val2, long mask)
> +{
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_OFFSET:
> +		ret = sun4i_gpadc_temp_offset(indio_dev, val);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_RAW:
> +		if (chan->type == IIO_VOLTAGE)
> +			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
> +						   val);
> +		else
> +			ret = sun4i_gpadc_temp_read(indio_dev, val);
> +
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		ret = sun4i_gpadc_temp_scale(indio_dev, val);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static const struct iio_info sun4i_gpadc_iio_info = {
> +	.read_raw = sun4i_gpadc_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
> +{
> +	struct sun4i_gpadc_iio *info = dev_id;
> +
> +	if (atomic_read(&info->ignore_temp_data_irq))
> +		return IRQ_HANDLED;
> +
> +	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
> +		complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
> +{
> +	struct sun4i_gpadc_iio *info = dev_id;
> +
> +	if (atomic_read(&info->ignore_fifo_data_irq))
> +		return IRQ_HANDLED;
> +
> +	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
> +		complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
> +
> +	/* Disable the ADC on IP */
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
> +	/* Disable temperature sensor on IP */
> +	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_runtime_resume(struct device *dev)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
> +
> +	/* clkin = 6MHz */
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
> +		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
> +		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
> +		     SUN4I_GPADC_CTRL0_T_ACQ(63));
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
> +		     SUN4I_GPADC_CTRL3_FILTER_EN |
> +		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
> +	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~1.3s */
> +	regmap_write(info->regmap, SUN4I_GPADC_TPR,
> +		     SUN4I_GPADC_TPR_TEMP_ENABLE |
> +		     SUN4I_GPADC_TPR_TEMP_PERIOD(1953));
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_get_temp(void *data, int *temp)
> +{
> +	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
> +	int val, scale, offset;
> +
> +	/* If reading temperature times out, take stored previous value. */
> +	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
> +		val = info->temp_data;
> +	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
> +	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
> +
> +	*temp = (val + offset) * scale;
> +
> +	return 0;
> +}
> +
> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
> +	.get_temp = &sun4i_gpadc_get_temp,
> +};
> +
> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
> +	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
> +	.runtime_resume = &sun4i_gpadc_runtime_resume,
> +};
> +
> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
> +			  irq_handler_t handler, const char *devname,
> +			  unsigned int *irq, atomic_t *atomic)
> +{
> +	int ret;
> +	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
> +
> +	/*
> +	 * Once the interrupt is activated, the IP continuously performs
> +	 * conversions thus throws interrupts. The interrupt is activated right
> +	 * after being requested but we want to control when these interrupts
> +	 * occur thus we disable it right after being requested. However, an
> +	 * interrupt might occur between these two instructions and we have to
> +	 * make sure that does not happen, by using atomic flags. We set the
> +	 * flag before requesting the interrupt and unset it right after
> +	 * disabling the interrupt. When an interrupt occurs between these two
> +	 * instructions, reading the atomic flag will tell us to ignore the
> +	 * interrupt.
> +	 */
> +	atomic_set(atomic, 1);
> +
> +	*irq = platform_get_irq_byname(pdev, name);
> +	if (*irq < 0) {
> +		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
> +		return *irq;
> +	}
> +
> +	*irq = regmap_irq_get_virq(mfd_dev->regmap_irqc, *irq);
> +	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
> +					   devname, info);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
> +			name, ret);
> +		return ret;
> +	}
> +
> +	disable_irq(*irq);
> +	atomic_set(atomic, 0);
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_probe(struct platform_device *pdev)
> +{
> +	struct sun4i_gpadc_iio *info;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
> +	struct thermal_zone_device *tzd;
> +
> +	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	info = iio_priv(indio_dev);
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	mutex_init(&info->mutex);
> +	info->regmap = sun4i_gpadc_dev->regmap;
> +	info->indio_dev = indio_dev;
> +	info->temp_data = -1;
> +	info->adc_data = -1;
> +	init_completion(&info->completion);
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &sun4i_gpadc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
> +	indio_dev->channels = sun4i_gpadc_channels;
> +
> +	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
> +
> +	/*
> +	 * This driver is a child of an MFD which has a node in the DT but not
> +	 * its children. Therefore, the resulting devices of this driver do not
> +	 * have an of_node variable.
> +	 * However, its parent (the MFD driver) has an of_node variable and
> +	 * since devm_thermal_zone_of_sensor_register uses its first argument to
> +	 * match the phandle defined in the node of the thermal driver with the
> +	 * of_node of the device passed as first argument and the third argument
> +	 * to call ops from thermal_zone_of_device_ops, the solution is to use
> +	 * the parent device as first argument to match the phandle with its
> +	 * of_node, and the device from this driver as third argument to return
> +	 * the temperature.
> +	 */
> +	tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
> +						   &sun4i_ts_tz_ops);
> +	if (IS_ERR(tzd)) {
> +		dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
> +			PTR_ERR(tzd));
> +		return PTR_ERR(tzd);
> +	}
> +
> +	pm_runtime_set_autosuspend_delay(&pdev->dev,
> +					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
> +	pm_runtime_use_autosuspend(&pdev->dev);
> +	pm_runtime_set_suspended(&pdev->dev);
> +	pm_runtime_enable(&pdev->dev);
> +
> +	ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
> +			     sun4i_gpadc_temp_data_irq_handler, "temp_data",
> +			     &info->temp_data_irq, &info->ignore_temp_data_irq);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
> +			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
> +			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed to register iio map array\n");
> +		goto err;
> +	}
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "could not register the device\n");
> +		goto err_map;
> +	}
> +
> +	return 0;
> +
> +err_map:
> +	iio_map_array_unregister(indio_dev);
> +
> +err:
> +	pm_runtime_put(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +
> +	return ret;
> +}
> +
> +static int sun4i_gpadc_remove(struct platform_device *pdev)
> +{
> +	struct sun4i_gpadc_iio *info;
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +
> +	pm_runtime_put(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +	info = iio_priv(indio_dev);
> +	iio_map_array_unregister(indio_dev);
> +	iio_device_unregister(indio_dev);
> +
> +	return 0;
> +}
> +
> +static const struct platform_device_id sun4i_gpadc_id[] = {
> +	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
> +	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
> +	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
> +	{ /* sentinel */ },
> +};
> +
> +static struct platform_driver sun4i_gpadc_driver = {
> +	.driver = {
> +		.name = "sun4i-gpadc-iio",
> +		.pm = &sun4i_gpadc_pm_ops,
> +	},
> +	.id_table = sun4i_gpadc_id,
> +	.probe = sun4i_gpadc_probe,
> +	.remove = sun4i_gpadc_remove,
> +};
> +
> +module_platform_driver(sun4i_gpadc_driver);
> +
> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_LICENSE("GPL v2");
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v6 2/2] iio: adc: add support for Allwinner SoCs ADC
@ 2016-10-30 16:59     ` Jonathan Cameron
  0 siblings, 0 replies; 14+ messages in thread
From: Jonathan Cameron @ 2016-10-30 16:59 UTC (permalink / raw)
  To: linux-arm-kernel

On 15/09/16 13:44, Quentin Schulz wrote:
> The Allwinner SoCs all have an ADC that can also act as a touchscreen
> controller and a thermal sensor. This patch adds the ADC driver which is
> based on the MFD for the same SoCs ADC.
> 
> This also registers the thermal adc channel in the iio map array so
> iio_hwmon could use it without modifying the Device Tree. This registers
> the driver in the thermal framework.
> 
> This driver probes on three different platform_device_id to take into
> account slight differences (registers bit and temperature computation)
> between Allwinner SoCs ADCs.
> 
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> Acked-by: Jonathan Cameron <jic23@kernel.org>
Hi Lee,

As you applied the MFD part of this series, could you pick this up as well?

Thanks,

Jonathan
> ---
> 
> v6:
>  - remove useless member (regs) from sun4i_gpadc_dev structure,
>  - rename sun4i_gpadc_dev structure to sun4i_gpadc_iio,
>  - remove regmap_update_bits used to disable hardware interrupts, it is already
>  handled by devm functions,
> 
> v5:
>  - correct mail address,
>  - correct several typos,
>  - move from const to static for sunxi_gpadc_chan_select functions,
>  - rename soc_specific struct to gpadc_data,
>  - rename soc_specific field to data in sun4i_gpadc_dev,
>  - return error code from regmap_write in case of failure in read_raws,
>  - share if condition in IIO_CHAN_INFO_RAW case,
>  - add comment on why we use parent device for registering in thermal,
>  - reordering remove function,
> 
> v4:
>  - rename files and variables from sunxi* to sun4i*,
>  - shorten sunxi_gpadc_soc_specific structure to soc_specific,
>  - factorize sysfs ADC and temp read_raws,
>  - use cached values when read_raw times out (except before a first value
>    is gotten),
>  - remove mutex locks and unlocks from runtime_pm functions,
>  - factorize irq initializations,
>  - initialize temp_data and fifo_data values to -1 (error value),
>  - "impersonate" MFD to register in thermal framework,
>  - deactivate hardware interrupts one by one when probe fails or when
>    removing driver instead of blindly deactivating all hardware interrupts,
>  - selects THERMAL_OF in Kconfig,
> 
> v3:
>  - correct wrapping,
>  - add comment about thermal sensor inner working,
>  - move defines in mfd header,
>  - use structure to define SoC specific registers or behaviour,
>  - attach this structure to the device according to of_device_id of the
>    platform device,
>  - use new mutex instead of iio_dev mutex,
>  - use atomic flags to avoid race between request_irq and disable_irq in
>    probe,
>  - switch from processed value to raw, offset and scale values for
>    temperature ADC channel,
>  - remove faulty sentinel in iio_chan_spec array,
>  - add pm_runtime support,
>  - register thermal sensor in thermal framework (forgotten since the
>    beginning whereas it is present in current sun4i-ts driver),
>  - remove useless ret variables to store return value of regmap_reads,
>  - move comments on thermal sensor acquisition period in code instead of
>    header,
>  - adding goto label to unregister iio_map_array when failing to register
>    iio_dev,
> 
> v2:
>  - add SUNXI_GPADC_ prefixes for defines,
>  - correct typo in Kconfig,
>  - reorder alphabetically includes, makefile,
>  - add license header,
>  - fix architecture variations not being handled in interrupt handlers or
>    read raw functions,
>  - fix unability to return negative values from thermal sensor,
>  - add gotos to reduce code repetition,
>  - fix irq variable being unsigned int instead of int,
>  - remove useless dev_err and dev_info,
>  - deactivate all interrupts if probe fails,
>  - fix iio_device_register on NULL variable,
>  - deactivate ADC in the IP when probe fails or when removing driver,
> 
>  drivers/iio/adc/Kconfig           |  13 +
>  drivers/iio/adc/Makefile          |   1 +
>  drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 536 insertions(+)
>  create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 25378c5..ea36a4f 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -384,6 +384,19 @@ config ROCKCHIP_SARADC
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called rockchip_saradc.
>  
> +config SUN4I_GPADC
> +	tristate "Support for the Allwinner SoCs GPADC"
> +	depends on IIO
> +	depends on MFD_SUN4I_GPADC
> +	select THERMAL_OF
> +	help
> +	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
> +	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
> +	  a touchscreen input and one channel for thermal sensor.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called sun4i-gpadc-iio.
> +
>  config TI_ADC081C
>  	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>  	depends on I2C
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 38638d4..204372d 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -37,6 +37,7 @@ 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
>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>  obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
> new file mode 100644
> index 0000000..e2c5ba8
> --- /dev/null
> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
> @@ -0,0 +1,522 @@
> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
> + *
> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
> + * Free Software Foundation.
> + *
> + * The Allwinner SoCs all have an ADC that can also act as a touchscreen
> + * controller and a thermal sensor.
> + * The thermal sensor works only when the ADC acts as a touchscreen controller
> + * and is configured to throw an interrupt every fixed periods of time (let say
> + * every X seconds).
> + * One would be tempted to disable the IP on the hardware side rather than
> + * disabling interrupts to save some power but that resets the internal clock of
> + * the IP, resulting in having to wait X seconds every time we want to read the
> + * value of the thermal sensor.
> + * This is also the reason of using autosuspend in pm_runtime. If there was no
> + * autosuspend, the thermal sensor would need X seconds after every
> + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
> + * thermal sensor to be requested again in a certain time span before it gets
> + * shutdown for not being used.
> + */
> +
> +#include <linux/completion.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/thermal.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/driver.h>
> +#include <linux/iio/machine.h>
> +#include <linux/mfd/sun4i-gpadc.h>
> +
> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
> +{
> +	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
> +}
> +
> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
> +{
> +	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
> +}
> +
> +struct gpadc_data {
> +	int		temp_offset;
> +	int		temp_scale;
> +	unsigned int	tp_mode_en;
> +	unsigned int	tp_adc_select;
> +	unsigned int	(*adc_chan_select)(unsigned int chan);
> +};
> +
> +static const struct gpadc_data sun4i_gpadc_data = {
> +	.temp_offset = -1932,
> +	.temp_scale = 133,
> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun4i_gpadc_chan_select,
> +};
> +
> +static const struct gpadc_data sun5i_gpadc_data = {
> +	.temp_offset = -1447,
> +	.temp_scale = 100,
> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun4i_gpadc_chan_select,
> +};
> +
> +static const struct gpadc_data sun6i_gpadc_data = {
> +	.temp_offset = -1623,
> +	.temp_scale = 167,
> +	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun6i_gpadc_chan_select,
> +};
> +
> +struct sun4i_gpadc_iio {
> +	struct iio_dev			*indio_dev;
> +	struct completion		completion;
> +	int				temp_data;
> +	u32				adc_data;
> +	struct regmap			*regmap;
> +	unsigned int			fifo_data_irq;
> +	atomic_t			ignore_fifo_data_irq;
> +	unsigned int			temp_data_irq;
> +	atomic_t			ignore_temp_data_irq;
> +	const struct gpadc_data		*data;
> +	/* prevents concurrent reads of temperature and ADC */
> +	struct mutex			mutex;
> +};
> +
> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
> +	.type = IIO_VOLTAGE,					\
> +	.indexed = 1,						\
> +	.channel = _channel,					\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
> +	.datasheet_name = _name,				\
> +}
> +
> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
> +	{
> +		.adc_channel_label = "temp_adc",
> +		.consumer_dev_name = "iio_hwmon.0",
> +	},
> +	{ /* sentinel */ },
> +};
> +
> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
> +	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
> +	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
> +	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
> +	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
> +	{
> +		.type = IIO_TEMP,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE) |
> +				      BIT(IIO_CHAN_INFO_OFFSET),
> +		.datasheet_name = "temp_adc",
> +	},
> +};
> +
> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
> +			    unsigned int irq)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +	int ret = 0;
> +
> +	pm_runtime_get_sync(indio_dev->dev.parent);
> +	mutex_lock(&info->mutex);
> +
> +	reinit_completion(&info->completion);
> +
> +	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
> +	if (ret)
> +		return ret;
> +
> +	if (irq == info->fifo_data_irq) {
> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
> +				   info->data->tp_mode_en |
> +				   info->data->tp_adc_select |
> +				   info->data->adc_chan_select(channel));
> +	} else {
> +		/*
> +		 * The temperature sensor returns valid data only when the ADC
> +		 * operates in touchscreen mode.
> +		 */
> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
> +				   info->data->tp_mode_en);
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	enable_irq(irq);
> +
> +	if (!wait_for_completion_timeout(&info->completion,
> +					 msecs_to_jiffies(100))) {
> +		if ((irq == info->fifo_data_irq && info->adc_data == -1) ||
> +		    (irq == info->temp_data_irq && info->temp_data == -1)) {
> +			ret = -ETIMEDOUT;
> +			goto out;
> +		}
> +	}
> +
> +	if (irq == info->fifo_data_irq)
> +		*val = info->adc_data;
> +	else
> +		*val = info->temp_data;
> +
> +	ret = 0;
> +
> +out:
> +	disable_irq(irq);
> +	mutex_unlock(&info->mutex);
> +	pm_runtime_mark_last_busy(indio_dev->dev.parent);
> +	pm_runtime_put_autosuspend(indio_dev->dev.parent);
> +
> +	return ret;
> +}
> +
> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
> +				int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
> +}
> +
> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
> +}
> +
> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	*val = info->data->temp_offset;
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	*val = info->data->temp_scale;
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan, int *val,
> +				int *val2, long mask)
> +{
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_OFFSET:
> +		ret = sun4i_gpadc_temp_offset(indio_dev, val);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_RAW:
> +		if (chan->type == IIO_VOLTAGE)
> +			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
> +						   val);
> +		else
> +			ret = sun4i_gpadc_temp_read(indio_dev, val);
> +
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		ret = sun4i_gpadc_temp_scale(indio_dev, val);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static const struct iio_info sun4i_gpadc_iio_info = {
> +	.read_raw = sun4i_gpadc_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
> +{
> +	struct sun4i_gpadc_iio *info = dev_id;
> +
> +	if (atomic_read(&info->ignore_temp_data_irq))
> +		return IRQ_HANDLED;
> +
> +	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
> +		complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
> +{
> +	struct sun4i_gpadc_iio *info = dev_id;
> +
> +	if (atomic_read(&info->ignore_fifo_data_irq))
> +		return IRQ_HANDLED;
> +
> +	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
> +		complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
> +
> +	/* Disable the ADC on IP */
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
> +	/* Disable temperature sensor on IP */
> +	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_runtime_resume(struct device *dev)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
> +
> +	/* clkin = 6MHz */
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
> +		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
> +		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
> +		     SUN4I_GPADC_CTRL0_T_ACQ(63));
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
> +		     SUN4I_GPADC_CTRL3_FILTER_EN |
> +		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
> +	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~1.3s */
> +	regmap_write(info->regmap, SUN4I_GPADC_TPR,
> +		     SUN4I_GPADC_TPR_TEMP_ENABLE |
> +		     SUN4I_GPADC_TPR_TEMP_PERIOD(1953));
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_get_temp(void *data, int *temp)
> +{
> +	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
> +	int val, scale, offset;
> +
> +	/* If reading temperature times out, take stored previous value. */
> +	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
> +		val = info->temp_data;
> +	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
> +	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
> +
> +	*temp = (val + offset) * scale;
> +
> +	return 0;
> +}
> +
> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
> +	.get_temp = &sun4i_gpadc_get_temp,
> +};
> +
> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
> +	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
> +	.runtime_resume = &sun4i_gpadc_runtime_resume,
> +};
> +
> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
> +			  irq_handler_t handler, const char *devname,
> +			  unsigned int *irq, atomic_t *atomic)
> +{
> +	int ret;
> +	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
> +
> +	/*
> +	 * Once the interrupt is activated, the IP continuously performs
> +	 * conversions thus throws interrupts. The interrupt is activated right
> +	 * after being requested but we want to control when these interrupts
> +	 * occur thus we disable it right after being requested. However, an
> +	 * interrupt might occur between these two instructions and we have to
> +	 * make sure that does not happen, by using atomic flags. We set the
> +	 * flag before requesting the interrupt and unset it right after
> +	 * disabling the interrupt. When an interrupt occurs between these two
> +	 * instructions, reading the atomic flag will tell us to ignore the
> +	 * interrupt.
> +	 */
> +	atomic_set(atomic, 1);
> +
> +	*irq = platform_get_irq_byname(pdev, name);
> +	if (*irq < 0) {
> +		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
> +		return *irq;
> +	}
> +
> +	*irq = regmap_irq_get_virq(mfd_dev->regmap_irqc, *irq);
> +	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
> +					   devname, info);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
> +			name, ret);
> +		return ret;
> +	}
> +
> +	disable_irq(*irq);
> +	atomic_set(atomic, 0);
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_probe(struct platform_device *pdev)
> +{
> +	struct sun4i_gpadc_iio *info;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
> +	struct thermal_zone_device *tzd;
> +
> +	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	info = iio_priv(indio_dev);
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	mutex_init(&info->mutex);
> +	info->regmap = sun4i_gpadc_dev->regmap;
> +	info->indio_dev = indio_dev;
> +	info->temp_data = -1;
> +	info->adc_data = -1;
> +	init_completion(&info->completion);
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &sun4i_gpadc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
> +	indio_dev->channels = sun4i_gpadc_channels;
> +
> +	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
> +
> +	/*
> +	 * This driver is a child of an MFD which has a node in the DT but not
> +	 * its children. Therefore, the resulting devices of this driver do not
> +	 * have an of_node variable.
> +	 * However, its parent (the MFD driver) has an of_node variable and
> +	 * since devm_thermal_zone_of_sensor_register uses its first argument to
> +	 * match the phandle defined in the node of the thermal driver with the
> +	 * of_node of the device passed as first argument and the third argument
> +	 * to call ops from thermal_zone_of_device_ops, the solution is to use
> +	 * the parent device as first argument to match the phandle with its
> +	 * of_node, and the device from this driver as third argument to return
> +	 * the temperature.
> +	 */
> +	tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
> +						   &sun4i_ts_tz_ops);
> +	if (IS_ERR(tzd)) {
> +		dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
> +			PTR_ERR(tzd));
> +		return PTR_ERR(tzd);
> +	}
> +
> +	pm_runtime_set_autosuspend_delay(&pdev->dev,
> +					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
> +	pm_runtime_use_autosuspend(&pdev->dev);
> +	pm_runtime_set_suspended(&pdev->dev);
> +	pm_runtime_enable(&pdev->dev);
> +
> +	ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
> +			     sun4i_gpadc_temp_data_irq_handler, "temp_data",
> +			     &info->temp_data_irq, &info->ignore_temp_data_irq);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
> +			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
> +			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed to register iio map array\n");
> +		goto err;
> +	}
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "could not register the device\n");
> +		goto err_map;
> +	}
> +
> +	return 0;
> +
> +err_map:
> +	iio_map_array_unregister(indio_dev);
> +
> +err:
> +	pm_runtime_put(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +
> +	return ret;
> +}
> +
> +static int sun4i_gpadc_remove(struct platform_device *pdev)
> +{
> +	struct sun4i_gpadc_iio *info;
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +
> +	pm_runtime_put(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +	info = iio_priv(indio_dev);
> +	iio_map_array_unregister(indio_dev);
> +	iio_device_unregister(indio_dev);
> +
> +	return 0;
> +}
> +
> +static const struct platform_device_id sun4i_gpadc_id[] = {
> +	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
> +	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
> +	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
> +	{ /* sentinel */ },
> +};
> +
> +static struct platform_driver sun4i_gpadc_driver = {
> +	.driver = {
> +		.name = "sun4i-gpadc-iio",
> +		.pm = &sun4i_gpadc_pm_ops,
> +	},
> +	.id_table = sun4i_gpadc_id,
> +	.probe = sun4i_gpadc_probe,
> +	.remove = sun4i_gpadc_remove,
> +};
> +
> +module_platform_driver(sun4i_gpadc_driver);
> +
> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_LICENSE("GPL v2");
> 

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v6 2/2] iio: adc: add support for Allwinner SoCs ADC
  2016-10-30 16:59     ` Jonathan Cameron
@ 2016-10-31  9:34       ` Quentin Schulz
  -1 siblings, 0 replies; 14+ messages in thread
From: Quentin Schulz @ 2016-10-31  9:34 UTC (permalink / raw)
  To: Jonathan Cameron, jdelvare, linux, knaack.h, lars, pmeerw,
	maxime.ripard, wens, lee.jones
  Cc: thomas.petazzoni, antoine.tenart, linux-kernel, linux-hwmon,
	linux-iio, linux-arm-kernel

Hi Jonathan and Lee,

On 30/10/2016 17:59, Jonathan Cameron wrote:
> On 15/09/16 13:44, Quentin Schulz wrote:
>> The Allwinner SoCs all have an ADC that can also act as a touchscreen
>> controller and a thermal sensor. This patch adds the ADC driver which is
>> based on the MFD for the same SoCs ADC.
>>
>> This also registers the thermal adc channel in the iio map array so
>> iio_hwmon could use it without modifying the Device Tree. This registers
>> the driver in the thermal framework.
>>
>> This driver probes on three different platform_device_id to take into
>> account slight differences (registers bit and temperature computation)
>> between Allwinner SoCs ADCs.
>>
>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>> Acked-by: Jonathan Cameron <jic23@kernel.org>
> Hi Lee,
> 
> As you applied the MFD part of this series, could you pick this up as well?
> 

Someone reported some bugs on the CHIP (sun5i-r8). I'm investigating that.

Also, it misses a scale for voltage (not that critical but still).

And also, Thomas Petazzoni just found a deadlock in sun4i_gpadc_read. If
a regmap_write fails after getting the mutex, it is never released.

Is there some sort of deadline you want for a v7 since the mfd patch has
been merged?

Thanks,
Quentin

> Thanks,
> 
> Jonathan
>> ---
>>
>> v6:
>>  - remove useless member (regs) from sun4i_gpadc_dev structure,
>>  - rename sun4i_gpadc_dev structure to sun4i_gpadc_iio,
>>  - remove regmap_update_bits used to disable hardware interrupts, it is already
>>  handled by devm functions,
>>
>> v5:
>>  - correct mail address,
>>  - correct several typos,
>>  - move from const to static for sunxi_gpadc_chan_select functions,
>>  - rename soc_specific struct to gpadc_data,
>>  - rename soc_specific field to data in sun4i_gpadc_dev,
>>  - return error code from regmap_write in case of failure in read_raws,
>>  - share if condition in IIO_CHAN_INFO_RAW case,
>>  - add comment on why we use parent device for registering in thermal,
>>  - reordering remove function,
>>
>> v4:
>>  - rename files and variables from sunxi* to sun4i*,
>>  - shorten sunxi_gpadc_soc_specific structure to soc_specific,
>>  - factorize sysfs ADC and temp read_raws,
>>  - use cached values when read_raw times out (except before a first value
>>    is gotten),
>>  - remove mutex locks and unlocks from runtime_pm functions,
>>  - factorize irq initializations,
>>  - initialize temp_data and fifo_data values to -1 (error value),
>>  - "impersonate" MFD to register in thermal framework,
>>  - deactivate hardware interrupts one by one when probe fails or when
>>    removing driver instead of blindly deactivating all hardware interrupts,
>>  - selects THERMAL_OF in Kconfig,
>>
>> v3:
>>  - correct wrapping,
>>  - add comment about thermal sensor inner working,
>>  - move defines in mfd header,
>>  - use structure to define SoC specific registers or behaviour,
>>  - attach this structure to the device according to of_device_id of the
>>    platform device,
>>  - use new mutex instead of iio_dev mutex,
>>  - use atomic flags to avoid race between request_irq and disable_irq in
>>    probe,
>>  - switch from processed value to raw, offset and scale values for
>>    temperature ADC channel,
>>  - remove faulty sentinel in iio_chan_spec array,
>>  - add pm_runtime support,
>>  - register thermal sensor in thermal framework (forgotten since the
>>    beginning whereas it is present in current sun4i-ts driver),
>>  - remove useless ret variables to store return value of regmap_reads,
>>  - move comments on thermal sensor acquisition period in code instead of
>>    header,
>>  - adding goto label to unregister iio_map_array when failing to register
>>    iio_dev,
>>
>> v2:
>>  - add SUNXI_GPADC_ prefixes for defines,
>>  - correct typo in Kconfig,
>>  - reorder alphabetically includes, makefile,
>>  - add license header,
>>  - fix architecture variations not being handled in interrupt handlers or
>>    read raw functions,
>>  - fix unability to return negative values from thermal sensor,
>>  - add gotos to reduce code repetition,
>>  - fix irq variable being unsigned int instead of int,
>>  - remove useless dev_err and dev_info,
>>  - deactivate all interrupts if probe fails,
>>  - fix iio_device_register on NULL variable,
>>  - deactivate ADC in the IP when probe fails or when removing driver,
>>
>>  drivers/iio/adc/Kconfig           |  13 +
>>  drivers/iio/adc/Makefile          |   1 +
>>  drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 536 insertions(+)
>>  create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index 25378c5..ea36a4f 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -384,6 +384,19 @@ config ROCKCHIP_SARADC
>>  	  To compile this driver as a module, choose M here: the
>>  	  module will be called rockchip_saradc.
>>  
>> +config SUN4I_GPADC
>> +	tristate "Support for the Allwinner SoCs GPADC"
>> +	depends on IIO
>> +	depends on MFD_SUN4I_GPADC
>> +	select THERMAL_OF
>> +	help
>> +	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
>> +	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
>> +	  a touchscreen input and one channel for thermal sensor.
>> +
>> +	  To compile this driver as a module, choose M here: the module will be
>> +	  called sun4i-gpadc-iio.
>> +
>>  config TI_ADC081C
>>  	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>>  	depends on I2C
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index 38638d4..204372d 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -37,6 +37,7 @@ 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
>>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>  obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
>> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
>> new file mode 100644
>> index 0000000..e2c5ba8
>> --- /dev/null
>> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
>> @@ -0,0 +1,522 @@
>> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
>> + *
>> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
>> + * Free Software Foundation.
>> + *
>> + * The Allwinner SoCs all have an ADC that can also act as a touchscreen
>> + * controller and a thermal sensor.
>> + * The thermal sensor works only when the ADC acts as a touchscreen controller
>> + * and is configured to throw an interrupt every fixed periods of time (let say
>> + * every X seconds).
>> + * One would be tempted to disable the IP on the hardware side rather than
>> + * disabling interrupts to save some power but that resets the internal clock of
>> + * the IP, resulting in having to wait X seconds every time we want to read the
>> + * value of the thermal sensor.
>> + * This is also the reason of using autosuspend in pm_runtime. If there was no
>> + * autosuspend, the thermal sensor would need X seconds after every
>> + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
>> + * thermal sensor to be requested again in a certain time span before it gets
>> + * shutdown for not being used.
>> + */
>> +
>> +#include <linux/completion.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/regmap.h>
>> +#include <linux/thermal.h>
>> +
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/driver.h>
>> +#include <linux/iio/machine.h>
>> +#include <linux/mfd/sun4i-gpadc.h>
>> +
>> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
>> +{
>> +	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>> +}
>> +
>> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
>> +{
>> +	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>> +}
>> +
>> +struct gpadc_data {
>> +	int		temp_offset;
>> +	int		temp_scale;
>> +	unsigned int	tp_mode_en;
>> +	unsigned int	tp_adc_select;
>> +	unsigned int	(*adc_chan_select)(unsigned int chan);
>> +};
>> +
>> +static const struct gpadc_data sun4i_gpadc_data = {
>> +	.temp_offset = -1932,
>> +	.temp_scale = 133,
>> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>> +	.adc_chan_select = &sun4i_gpadc_chan_select,
>> +};
>> +
>> +static const struct gpadc_data sun5i_gpadc_data = {
>> +	.temp_offset = -1447,
>> +	.temp_scale = 100,
>> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>> +	.adc_chan_select = &sun4i_gpadc_chan_select,
>> +};
>> +
>> +static const struct gpadc_data sun6i_gpadc_data = {
>> +	.temp_offset = -1623,
>> +	.temp_scale = 167,
>> +	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
>> +	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
>> +	.adc_chan_select = &sun6i_gpadc_chan_select,
>> +};
>> +
>> +struct sun4i_gpadc_iio {
>> +	struct iio_dev			*indio_dev;
>> +	struct completion		completion;
>> +	int				temp_data;
>> +	u32				adc_data;
>> +	struct regmap			*regmap;
>> +	unsigned int			fifo_data_irq;
>> +	atomic_t			ignore_fifo_data_irq;
>> +	unsigned int			temp_data_irq;
>> +	atomic_t			ignore_temp_data_irq;
>> +	const struct gpadc_data		*data;
>> +	/* prevents concurrent reads of temperature and ADC */
>> +	struct mutex			mutex;
>> +};
>> +
>> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
>> +	.type = IIO_VOLTAGE,					\
>> +	.indexed = 1,						\
>> +	.channel = _channel,					\
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
>> +	.datasheet_name = _name,				\
>> +}
>> +
>> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
>> +	{
>> +		.adc_channel_label = "temp_adc",
>> +		.consumer_dev_name = "iio_hwmon.0",
>> +	},
>> +	{ /* sentinel */ },
>> +};
>> +
>> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
>> +	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>> +	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>> +	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>> +	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>> +	{
>> +		.type = IIO_TEMP,
>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>> +				      BIT(IIO_CHAN_INFO_SCALE) |
>> +				      BIT(IIO_CHAN_INFO_OFFSET),
>> +		.datasheet_name = "temp_adc",
>> +	},
>> +};
>> +
>> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
>> +			    unsigned int irq)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +	int ret = 0;
>> +
>> +	pm_runtime_get_sync(indio_dev->dev.parent);
>> +	mutex_lock(&info->mutex);
>> +
>> +	reinit_completion(&info->completion);
>> +
>> +	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
>> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
>> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (irq == info->fifo_data_irq) {
>> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>> +				   info->data->tp_mode_en |
>> +				   info->data->tp_adc_select |
>> +				   info->data->adc_chan_select(channel));
>> +	} else {
>> +		/*
>> +		 * The temperature sensor returns valid data only when the ADC
>> +		 * operates in touchscreen mode.
>> +		 */
>> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>> +				   info->data->tp_mode_en);
>> +	}
>> +
>> +	if (ret)
>> +		return ret;
>> +
>> +	enable_irq(irq);
>> +
>> +	if (!wait_for_completion_timeout(&info->completion,
>> +					 msecs_to_jiffies(100))) {
>> +		if ((irq == info->fifo_data_irq && info->adc_data == -1) ||
>> +		    (irq == info->temp_data_irq && info->temp_data == -1)) {
>> +			ret = -ETIMEDOUT;
>> +			goto out;
>> +		}
>> +	}
>> +
>> +	if (irq == info->fifo_data_irq)
>> +		*val = info->adc_data;
>> +	else
>> +		*val = info->temp_data;
>> +
>> +	ret = 0;
>> +
>> +out:
>> +	disable_irq(irq);
>> +	mutex_unlock(&info->mutex);
>> +	pm_runtime_mark_last_busy(indio_dev->dev.parent);
>> +	pm_runtime_put_autosuspend(indio_dev->dev.parent);
>> +
>> +	return ret;
>> +}
>> +
>> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
>> +				int *val)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +
>> +	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
>> +}
>> +
>> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +
>> +	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
>> +}
>> +
>> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +
>> +	*val = info->data->temp_offset;
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +
>> +	*val = info->data->temp_scale;
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
>> +				struct iio_chan_spec const *chan, int *val,
>> +				int *val2, long mask)
>> +{
>> +	int ret;
>> +
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_OFFSET:
>> +		ret = sun4i_gpadc_temp_offset(indio_dev, val);
>> +		if (ret)
>> +			return ret;
>> +
>> +		return IIO_VAL_INT;
>> +	case IIO_CHAN_INFO_RAW:
>> +		if (chan->type == IIO_VOLTAGE)
>> +			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
>> +						   val);
>> +		else
>> +			ret = sun4i_gpadc_temp_read(indio_dev, val);
>> +
>> +		if (ret)
>> +			return ret;
>> +
>> +		return IIO_VAL_INT;
>> +	case IIO_CHAN_INFO_SCALE:
>> +		ret = sun4i_gpadc_temp_scale(indio_dev, val);
>> +		if (ret)
>> +			return ret;
>> +
>> +		return IIO_VAL_INT;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static const struct iio_info sun4i_gpadc_iio_info = {
>> +	.read_raw = sun4i_gpadc_read_raw,
>> +	.driver_module = THIS_MODULE,
>> +};
>> +
>> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
>> +{
>> +	struct sun4i_gpadc_iio *info = dev_id;
>> +
>> +	if (atomic_read(&info->ignore_temp_data_irq))
>> +		return IRQ_HANDLED;
>> +
>> +	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
>> +		complete(&info->completion);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
>> +{
>> +	struct sun4i_gpadc_iio *info = dev_id;
>> +
>> +	if (atomic_read(&info->ignore_fifo_data_irq))
>> +		return IRQ_HANDLED;
>> +
>> +	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
>> +		complete(&info->completion);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>> +
>> +	/* Disable the ADC on IP */
>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
>> +	/* Disable temperature sensor on IP */
>> +	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_runtime_resume(struct device *dev)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>> +
>> +	/* clkin = 6MHz */
>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
>> +		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
>> +		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
>> +		     SUN4I_GPADC_CTRL0_T_ACQ(63));
>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
>> +		     SUN4I_GPADC_CTRL3_FILTER_EN |
>> +		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
>> +	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~1.3s */
>> +	regmap_write(info->regmap, SUN4I_GPADC_TPR,
>> +		     SUN4I_GPADC_TPR_TEMP_ENABLE |
>> +		     SUN4I_GPADC_TPR_TEMP_PERIOD(1953));
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_get_temp(void *data, int *temp)
>> +{
>> +	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
>> +	int val, scale, offset;
>> +
>> +	/* If reading temperature times out, take stored previous value. */
>> +	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
>> +		val = info->temp_data;
>> +	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
>> +	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
>> +
>> +	*temp = (val + offset) * scale;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
>> +	.get_temp = &sun4i_gpadc_get_temp,
>> +};
>> +
>> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
>> +	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
>> +	.runtime_resume = &sun4i_gpadc_runtime_resume,
>> +};
>> +
>> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
>> +			  irq_handler_t handler, const char *devname,
>> +			  unsigned int *irq, atomic_t *atomic)
>> +{
>> +	int ret;
>> +	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
>> +
>> +	/*
>> +	 * Once the interrupt is activated, the IP continuously performs
>> +	 * conversions thus throws interrupts. The interrupt is activated right
>> +	 * after being requested but we want to control when these interrupts
>> +	 * occur thus we disable it right after being requested. However, an
>> +	 * interrupt might occur between these two instructions and we have to
>> +	 * make sure that does not happen, by using atomic flags. We set the
>> +	 * flag before requesting the interrupt and unset it right after
>> +	 * disabling the interrupt. When an interrupt occurs between these two
>> +	 * instructions, reading the atomic flag will tell us to ignore the
>> +	 * interrupt.
>> +	 */
>> +	atomic_set(atomic, 1);
>> +
>> +	*irq = platform_get_irq_byname(pdev, name);
>> +	if (*irq < 0) {
>> +		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
>> +		return *irq;
>> +	}
>> +
>> +	*irq = regmap_irq_get_virq(mfd_dev->regmap_irqc, *irq);
>> +	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
>> +					   devname, info);
>> +	if (ret < 0) {
>> +		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
>> +			name, ret);
>> +		return ret;
>> +	}
>> +
>> +	disable_irq(*irq);
>> +	atomic_set(atomic, 0);
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_probe(struct platform_device *pdev)
>> +{
>> +	struct sun4i_gpadc_iio *info;
>> +	struct iio_dev *indio_dev;
>> +	int ret;
>> +	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
>> +	struct thermal_zone_device *tzd;
>> +
>> +	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
>> +
>> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>> +	if (!indio_dev)
>> +		return -ENOMEM;
>> +
>> +	info = iio_priv(indio_dev);
>> +	platform_set_drvdata(pdev, indio_dev);
>> +
>> +	mutex_init(&info->mutex);
>> +	info->regmap = sun4i_gpadc_dev->regmap;
>> +	info->indio_dev = indio_dev;
>> +	info->temp_data = -1;
>> +	info->adc_data = -1;
>> +	init_completion(&info->completion);
>> +	indio_dev->name = dev_name(&pdev->dev);
>> +	indio_dev->dev.parent = &pdev->dev;
>> +	indio_dev->dev.of_node = pdev->dev.of_node;
>> +	indio_dev->info = &sun4i_gpadc_iio_info;
>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>> +	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
>> +	indio_dev->channels = sun4i_gpadc_channels;
>> +
>> +	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
>> +
>> +	/*
>> +	 * This driver is a child of an MFD which has a node in the DT but not
>> +	 * its children. Therefore, the resulting devices of this driver do not
>> +	 * have an of_node variable.
>> +	 * However, its parent (the MFD driver) has an of_node variable and
>> +	 * since devm_thermal_zone_of_sensor_register uses its first argument to
>> +	 * match the phandle defined in the node of the thermal driver with the
>> +	 * of_node of the device passed as first argument and the third argument
>> +	 * to call ops from thermal_zone_of_device_ops, the solution is to use
>> +	 * the parent device as first argument to match the phandle with its
>> +	 * of_node, and the device from this driver as third argument to return
>> +	 * the temperature.
>> +	 */
>> +	tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
>> +						   &sun4i_ts_tz_ops);
>> +	if (IS_ERR(tzd)) {
>> +		dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
>> +			PTR_ERR(tzd));
>> +		return PTR_ERR(tzd);
>> +	}
>> +
>> +	pm_runtime_set_autosuspend_delay(&pdev->dev,
>> +					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
>> +	pm_runtime_use_autosuspend(&pdev->dev);
>> +	pm_runtime_set_suspended(&pdev->dev);
>> +	pm_runtime_enable(&pdev->dev);
>> +
>> +	ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
>> +			     sun4i_gpadc_temp_data_irq_handler, "temp_data",
>> +			     &info->temp_data_irq, &info->ignore_temp_data_irq);
>> +	if (ret < 0)
>> +		goto err;
>> +
>> +	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
>> +			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
>> +			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
>> +	if (ret < 0)
>> +		goto err;
>> +
>> +	ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
>> +	if (ret < 0) {
>> +		dev_err(&pdev->dev, "failed to register iio map array\n");
>> +		goto err;
>> +	}
>> +
>> +	ret = iio_device_register(indio_dev);
>> +	if (ret < 0) {
>> +		dev_err(&pdev->dev, "could not register the device\n");
>> +		goto err_map;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_map:
>> +	iio_map_array_unregister(indio_dev);
>> +
>> +err:
>> +	pm_runtime_put(&pdev->dev);
>> +	pm_runtime_disable(&pdev->dev);
>> +
>> +	return ret;
>> +}
>> +
>> +static int sun4i_gpadc_remove(struct platform_device *pdev)
>> +{
>> +	struct sun4i_gpadc_iio *info;
>> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> +
>> +	pm_runtime_put(&pdev->dev);
>> +	pm_runtime_disable(&pdev->dev);
>> +	info = iio_priv(indio_dev);
>> +	iio_map_array_unregister(indio_dev);
>> +	iio_device_unregister(indio_dev);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct platform_device_id sun4i_gpadc_id[] = {
>> +	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
>> +	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
>> +	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
>> +	{ /* sentinel */ },
>> +};
>> +
>> +static struct platform_driver sun4i_gpadc_driver = {
>> +	.driver = {
>> +		.name = "sun4i-gpadc-iio",
>> +		.pm = &sun4i_gpadc_pm_ops,
>> +	},
>> +	.id_table = sun4i_gpadc_id,
>> +	.probe = sun4i_gpadc_probe,
>> +	.remove = sun4i_gpadc_remove,
>> +};
>> +
>> +module_platform_driver(sun4i_gpadc_driver);
>> +
>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
>> +MODULE_LICENSE("GPL v2");
>>
> 

-- 
Quentin Schulz, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v6 2/2] iio: adc: add support for Allwinner SoCs ADC
@ 2016-10-31  9:34       ` Quentin Schulz
  0 siblings, 0 replies; 14+ messages in thread
From: Quentin Schulz @ 2016-10-31  9:34 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Jonathan and Lee,

On 30/10/2016 17:59, Jonathan Cameron wrote:
> On 15/09/16 13:44, Quentin Schulz wrote:
>> The Allwinner SoCs all have an ADC that can also act as a touchscreen
>> controller and a thermal sensor. This patch adds the ADC driver which is
>> based on the MFD for the same SoCs ADC.
>>
>> This also registers the thermal adc channel in the iio map array so
>> iio_hwmon could use it without modifying the Device Tree. This registers
>> the driver in the thermal framework.
>>
>> This driver probes on three different platform_device_id to take into
>> account slight differences (registers bit and temperature computation)
>> between Allwinner SoCs ADCs.
>>
>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>> Acked-by: Jonathan Cameron <jic23@kernel.org>
> Hi Lee,
> 
> As you applied the MFD part of this series, could you pick this up as well?
> 

Someone reported some bugs on the CHIP (sun5i-r8). I'm investigating that.

Also, it misses a scale for voltage (not that critical but still).

And also, Thomas Petazzoni just found a deadlock in sun4i_gpadc_read. If
a regmap_write fails after getting the mutex, it is never released.

Is there some sort of deadline you want for a v7 since the mfd patch has
been merged?

Thanks,
Quentin

> Thanks,
> 
> Jonathan
>> ---
>>
>> v6:
>>  - remove useless member (regs) from sun4i_gpadc_dev structure,
>>  - rename sun4i_gpadc_dev structure to sun4i_gpadc_iio,
>>  - remove regmap_update_bits used to disable hardware interrupts, it is already
>>  handled by devm functions,
>>
>> v5:
>>  - correct mail address,
>>  - correct several typos,
>>  - move from const to static for sunxi_gpadc_chan_select functions,
>>  - rename soc_specific struct to gpadc_data,
>>  - rename soc_specific field to data in sun4i_gpadc_dev,
>>  - return error code from regmap_write in case of failure in read_raws,
>>  - share if condition in IIO_CHAN_INFO_RAW case,
>>  - add comment on why we use parent device for registering in thermal,
>>  - reordering remove function,
>>
>> v4:
>>  - rename files and variables from sunxi* to sun4i*,
>>  - shorten sunxi_gpadc_soc_specific structure to soc_specific,
>>  - factorize sysfs ADC and temp read_raws,
>>  - use cached values when read_raw times out (except before a first value
>>    is gotten),
>>  - remove mutex locks and unlocks from runtime_pm functions,
>>  - factorize irq initializations,
>>  - initialize temp_data and fifo_data values to -1 (error value),
>>  - "impersonate" MFD to register in thermal framework,
>>  - deactivate hardware interrupts one by one when probe fails or when
>>    removing driver instead of blindly deactivating all hardware interrupts,
>>  - selects THERMAL_OF in Kconfig,
>>
>> v3:
>>  - correct wrapping,
>>  - add comment about thermal sensor inner working,
>>  - move defines in mfd header,
>>  - use structure to define SoC specific registers or behaviour,
>>  - attach this structure to the device according to of_device_id of the
>>    platform device,
>>  - use new mutex instead of iio_dev mutex,
>>  - use atomic flags to avoid race between request_irq and disable_irq in
>>    probe,
>>  - switch from processed value to raw, offset and scale values for
>>    temperature ADC channel,
>>  - remove faulty sentinel in iio_chan_spec array,
>>  - add pm_runtime support,
>>  - register thermal sensor in thermal framework (forgotten since the
>>    beginning whereas it is present in current sun4i-ts driver),
>>  - remove useless ret variables to store return value of regmap_reads,
>>  - move comments on thermal sensor acquisition period in code instead of
>>    header,
>>  - adding goto label to unregister iio_map_array when failing to register
>>    iio_dev,
>>
>> v2:
>>  - add SUNXI_GPADC_ prefixes for defines,
>>  - correct typo in Kconfig,
>>  - reorder alphabetically includes, makefile,
>>  - add license header,
>>  - fix architecture variations not being handled in interrupt handlers or
>>    read raw functions,
>>  - fix unability to return negative values from thermal sensor,
>>  - add gotos to reduce code repetition,
>>  - fix irq variable being unsigned int instead of int,
>>  - remove useless dev_err and dev_info,
>>  - deactivate all interrupts if probe fails,
>>  - fix iio_device_register on NULL variable,
>>  - deactivate ADC in the IP when probe fails or when removing driver,
>>
>>  drivers/iio/adc/Kconfig           |  13 +
>>  drivers/iio/adc/Makefile          |   1 +
>>  drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 536 insertions(+)
>>  create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index 25378c5..ea36a4f 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -384,6 +384,19 @@ config ROCKCHIP_SARADC
>>  	  To compile this driver as a module, choose M here: the
>>  	  module will be called rockchip_saradc.
>>  
>> +config SUN4I_GPADC
>> +	tristate "Support for the Allwinner SoCs GPADC"
>> +	depends on IIO
>> +	depends on MFD_SUN4I_GPADC
>> +	select THERMAL_OF
>> +	help
>> +	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
>> +	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
>> +	  a touchscreen input and one channel for thermal sensor.
>> +
>> +	  To compile this driver as a module, choose M here: the module will be
>> +	  called sun4i-gpadc-iio.
>> +
>>  config TI_ADC081C
>>  	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>>  	depends on I2C
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index 38638d4..204372d 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -37,6 +37,7 @@ 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
>>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>  obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
>> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
>> new file mode 100644
>> index 0000000..e2c5ba8
>> --- /dev/null
>> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
>> @@ -0,0 +1,522 @@
>> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
>> + *
>> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
>> + * Free Software Foundation.
>> + *
>> + * The Allwinner SoCs all have an ADC that can also act as a touchscreen
>> + * controller and a thermal sensor.
>> + * The thermal sensor works only when the ADC acts as a touchscreen controller
>> + * and is configured to throw an interrupt every fixed periods of time (let say
>> + * every X seconds).
>> + * One would be tempted to disable the IP on the hardware side rather than
>> + * disabling interrupts to save some power but that resets the internal clock of
>> + * the IP, resulting in having to wait X seconds every time we want to read the
>> + * value of the thermal sensor.
>> + * This is also the reason of using autosuspend in pm_runtime. If there was no
>> + * autosuspend, the thermal sensor would need X seconds after every
>> + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
>> + * thermal sensor to be requested again in a certain time span before it gets
>> + * shutdown for not being used.
>> + */
>> +
>> +#include <linux/completion.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/regmap.h>
>> +#include <linux/thermal.h>
>> +
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/driver.h>
>> +#include <linux/iio/machine.h>
>> +#include <linux/mfd/sun4i-gpadc.h>
>> +
>> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
>> +{
>> +	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>> +}
>> +
>> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
>> +{
>> +	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>> +}
>> +
>> +struct gpadc_data {
>> +	int		temp_offset;
>> +	int		temp_scale;
>> +	unsigned int	tp_mode_en;
>> +	unsigned int	tp_adc_select;
>> +	unsigned int	(*adc_chan_select)(unsigned int chan);
>> +};
>> +
>> +static const struct gpadc_data sun4i_gpadc_data = {
>> +	.temp_offset = -1932,
>> +	.temp_scale = 133,
>> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>> +	.adc_chan_select = &sun4i_gpadc_chan_select,
>> +};
>> +
>> +static const struct gpadc_data sun5i_gpadc_data = {
>> +	.temp_offset = -1447,
>> +	.temp_scale = 100,
>> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>> +	.adc_chan_select = &sun4i_gpadc_chan_select,
>> +};
>> +
>> +static const struct gpadc_data sun6i_gpadc_data = {
>> +	.temp_offset = -1623,
>> +	.temp_scale = 167,
>> +	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
>> +	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
>> +	.adc_chan_select = &sun6i_gpadc_chan_select,
>> +};
>> +
>> +struct sun4i_gpadc_iio {
>> +	struct iio_dev			*indio_dev;
>> +	struct completion		completion;
>> +	int				temp_data;
>> +	u32				adc_data;
>> +	struct regmap			*regmap;
>> +	unsigned int			fifo_data_irq;
>> +	atomic_t			ignore_fifo_data_irq;
>> +	unsigned int			temp_data_irq;
>> +	atomic_t			ignore_temp_data_irq;
>> +	const struct gpadc_data		*data;
>> +	/* prevents concurrent reads of temperature and ADC */
>> +	struct mutex			mutex;
>> +};
>> +
>> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
>> +	.type = IIO_VOLTAGE,					\
>> +	.indexed = 1,						\
>> +	.channel = _channel,					\
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
>> +	.datasheet_name = _name,				\
>> +}
>> +
>> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
>> +	{
>> +		.adc_channel_label = "temp_adc",
>> +		.consumer_dev_name = "iio_hwmon.0",
>> +	},
>> +	{ /* sentinel */ },
>> +};
>> +
>> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
>> +	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>> +	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>> +	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>> +	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>> +	{
>> +		.type = IIO_TEMP,
>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>> +				      BIT(IIO_CHAN_INFO_SCALE) |
>> +				      BIT(IIO_CHAN_INFO_OFFSET),
>> +		.datasheet_name = "temp_adc",
>> +	},
>> +};
>> +
>> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
>> +			    unsigned int irq)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +	int ret = 0;
>> +
>> +	pm_runtime_get_sync(indio_dev->dev.parent);
>> +	mutex_lock(&info->mutex);
>> +
>> +	reinit_completion(&info->completion);
>> +
>> +	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
>> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
>> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (irq == info->fifo_data_irq) {
>> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>> +				   info->data->tp_mode_en |
>> +				   info->data->tp_adc_select |
>> +				   info->data->adc_chan_select(channel));
>> +	} else {
>> +		/*
>> +		 * The temperature sensor returns valid data only when the ADC
>> +		 * operates in touchscreen mode.
>> +		 */
>> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>> +				   info->data->tp_mode_en);
>> +	}
>> +
>> +	if (ret)
>> +		return ret;
>> +
>> +	enable_irq(irq);
>> +
>> +	if (!wait_for_completion_timeout(&info->completion,
>> +					 msecs_to_jiffies(100))) {
>> +		if ((irq == info->fifo_data_irq && info->adc_data == -1) ||
>> +		    (irq == info->temp_data_irq && info->temp_data == -1)) {
>> +			ret = -ETIMEDOUT;
>> +			goto out;
>> +		}
>> +	}
>> +
>> +	if (irq == info->fifo_data_irq)
>> +		*val = info->adc_data;
>> +	else
>> +		*val = info->temp_data;
>> +
>> +	ret = 0;
>> +
>> +out:
>> +	disable_irq(irq);
>> +	mutex_unlock(&info->mutex);
>> +	pm_runtime_mark_last_busy(indio_dev->dev.parent);
>> +	pm_runtime_put_autosuspend(indio_dev->dev.parent);
>> +
>> +	return ret;
>> +}
>> +
>> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
>> +				int *val)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +
>> +	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
>> +}
>> +
>> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +
>> +	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
>> +}
>> +
>> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +
>> +	*val = info->data->temp_offset;
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>> +
>> +	*val = info->data->temp_scale;
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
>> +				struct iio_chan_spec const *chan, int *val,
>> +				int *val2, long mask)
>> +{
>> +	int ret;
>> +
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_OFFSET:
>> +		ret = sun4i_gpadc_temp_offset(indio_dev, val);
>> +		if (ret)
>> +			return ret;
>> +
>> +		return IIO_VAL_INT;
>> +	case IIO_CHAN_INFO_RAW:
>> +		if (chan->type == IIO_VOLTAGE)
>> +			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
>> +						   val);
>> +		else
>> +			ret = sun4i_gpadc_temp_read(indio_dev, val);
>> +
>> +		if (ret)
>> +			return ret;
>> +
>> +		return IIO_VAL_INT;
>> +	case IIO_CHAN_INFO_SCALE:
>> +		ret = sun4i_gpadc_temp_scale(indio_dev, val);
>> +		if (ret)
>> +			return ret;
>> +
>> +		return IIO_VAL_INT;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static const struct iio_info sun4i_gpadc_iio_info = {
>> +	.read_raw = sun4i_gpadc_read_raw,
>> +	.driver_module = THIS_MODULE,
>> +};
>> +
>> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
>> +{
>> +	struct sun4i_gpadc_iio *info = dev_id;
>> +
>> +	if (atomic_read(&info->ignore_temp_data_irq))
>> +		return IRQ_HANDLED;
>> +
>> +	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
>> +		complete(&info->completion);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
>> +{
>> +	struct sun4i_gpadc_iio *info = dev_id;
>> +
>> +	if (atomic_read(&info->ignore_fifo_data_irq))
>> +		return IRQ_HANDLED;
>> +
>> +	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
>> +		complete(&info->completion);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>> +
>> +	/* Disable the ADC on IP */
>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
>> +	/* Disable temperature sensor on IP */
>> +	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_runtime_resume(struct device *dev)
>> +{
>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>> +
>> +	/* clkin = 6MHz */
>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
>> +		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
>> +		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
>> +		     SUN4I_GPADC_CTRL0_T_ACQ(63));
>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
>> +		     SUN4I_GPADC_CTRL3_FILTER_EN |
>> +		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
>> +	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~1.3s */
>> +	regmap_write(info->regmap, SUN4I_GPADC_TPR,
>> +		     SUN4I_GPADC_TPR_TEMP_ENABLE |
>> +		     SUN4I_GPADC_TPR_TEMP_PERIOD(1953));
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_get_temp(void *data, int *temp)
>> +{
>> +	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
>> +	int val, scale, offset;
>> +
>> +	/* If reading temperature times out, take stored previous value. */
>> +	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
>> +		val = info->temp_data;
>> +	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
>> +	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
>> +
>> +	*temp = (val + offset) * scale;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
>> +	.get_temp = &sun4i_gpadc_get_temp,
>> +};
>> +
>> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
>> +	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
>> +	.runtime_resume = &sun4i_gpadc_runtime_resume,
>> +};
>> +
>> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
>> +			  irq_handler_t handler, const char *devname,
>> +			  unsigned int *irq, atomic_t *atomic)
>> +{
>> +	int ret;
>> +	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
>> +
>> +	/*
>> +	 * Once the interrupt is activated, the IP continuously performs
>> +	 * conversions thus throws interrupts. The interrupt is activated right
>> +	 * after being requested but we want to control when these interrupts
>> +	 * occur thus we disable it right after being requested. However, an
>> +	 * interrupt might occur between these two instructions and we have to
>> +	 * make sure that does not happen, by using atomic flags. We set the
>> +	 * flag before requesting the interrupt and unset it right after
>> +	 * disabling the interrupt. When an interrupt occurs between these two
>> +	 * instructions, reading the atomic flag will tell us to ignore the
>> +	 * interrupt.
>> +	 */
>> +	atomic_set(atomic, 1);
>> +
>> +	*irq = platform_get_irq_byname(pdev, name);
>> +	if (*irq < 0) {
>> +		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
>> +		return *irq;
>> +	}
>> +
>> +	*irq = regmap_irq_get_virq(mfd_dev->regmap_irqc, *irq);
>> +	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
>> +					   devname, info);
>> +	if (ret < 0) {
>> +		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
>> +			name, ret);
>> +		return ret;
>> +	}
>> +
>> +	disable_irq(*irq);
>> +	atomic_set(atomic, 0);
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun4i_gpadc_probe(struct platform_device *pdev)
>> +{
>> +	struct sun4i_gpadc_iio *info;
>> +	struct iio_dev *indio_dev;
>> +	int ret;
>> +	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
>> +	struct thermal_zone_device *tzd;
>> +
>> +	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
>> +
>> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>> +	if (!indio_dev)
>> +		return -ENOMEM;
>> +
>> +	info = iio_priv(indio_dev);
>> +	platform_set_drvdata(pdev, indio_dev);
>> +
>> +	mutex_init(&info->mutex);
>> +	info->regmap = sun4i_gpadc_dev->regmap;
>> +	info->indio_dev = indio_dev;
>> +	info->temp_data = -1;
>> +	info->adc_data = -1;
>> +	init_completion(&info->completion);
>> +	indio_dev->name = dev_name(&pdev->dev);
>> +	indio_dev->dev.parent = &pdev->dev;
>> +	indio_dev->dev.of_node = pdev->dev.of_node;
>> +	indio_dev->info = &sun4i_gpadc_iio_info;
>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>> +	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
>> +	indio_dev->channels = sun4i_gpadc_channels;
>> +
>> +	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
>> +
>> +	/*
>> +	 * This driver is a child of an MFD which has a node in the DT but not
>> +	 * its children. Therefore, the resulting devices of this driver do not
>> +	 * have an of_node variable.
>> +	 * However, its parent (the MFD driver) has an of_node variable and
>> +	 * since devm_thermal_zone_of_sensor_register uses its first argument to
>> +	 * match the phandle defined in the node of the thermal driver with the
>> +	 * of_node of the device passed as first argument and the third argument
>> +	 * to call ops from thermal_zone_of_device_ops, the solution is to use
>> +	 * the parent device as first argument to match the phandle with its
>> +	 * of_node, and the device from this driver as third argument to return
>> +	 * the temperature.
>> +	 */
>> +	tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
>> +						   &sun4i_ts_tz_ops);
>> +	if (IS_ERR(tzd)) {
>> +		dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
>> +			PTR_ERR(tzd));
>> +		return PTR_ERR(tzd);
>> +	}
>> +
>> +	pm_runtime_set_autosuspend_delay(&pdev->dev,
>> +					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
>> +	pm_runtime_use_autosuspend(&pdev->dev);
>> +	pm_runtime_set_suspended(&pdev->dev);
>> +	pm_runtime_enable(&pdev->dev);
>> +
>> +	ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
>> +			     sun4i_gpadc_temp_data_irq_handler, "temp_data",
>> +			     &info->temp_data_irq, &info->ignore_temp_data_irq);
>> +	if (ret < 0)
>> +		goto err;
>> +
>> +	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
>> +			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
>> +			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
>> +	if (ret < 0)
>> +		goto err;
>> +
>> +	ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
>> +	if (ret < 0) {
>> +		dev_err(&pdev->dev, "failed to register iio map array\n");
>> +		goto err;
>> +	}
>> +
>> +	ret = iio_device_register(indio_dev);
>> +	if (ret < 0) {
>> +		dev_err(&pdev->dev, "could not register the device\n");
>> +		goto err_map;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_map:
>> +	iio_map_array_unregister(indio_dev);
>> +
>> +err:
>> +	pm_runtime_put(&pdev->dev);
>> +	pm_runtime_disable(&pdev->dev);
>> +
>> +	return ret;
>> +}
>> +
>> +static int sun4i_gpadc_remove(struct platform_device *pdev)
>> +{
>> +	struct sun4i_gpadc_iio *info;
>> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> +
>> +	pm_runtime_put(&pdev->dev);
>> +	pm_runtime_disable(&pdev->dev);
>> +	info = iio_priv(indio_dev);
>> +	iio_map_array_unregister(indio_dev);
>> +	iio_device_unregister(indio_dev);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct platform_device_id sun4i_gpadc_id[] = {
>> +	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
>> +	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
>> +	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
>> +	{ /* sentinel */ },
>> +};
>> +
>> +static struct platform_driver sun4i_gpadc_driver = {
>> +	.driver = {
>> +		.name = "sun4i-gpadc-iio",
>> +		.pm = &sun4i_gpadc_pm_ops,
>> +	},
>> +	.id_table = sun4i_gpadc_id,
>> +	.probe = sun4i_gpadc_probe,
>> +	.remove = sun4i_gpadc_remove,
>> +};
>> +
>> +module_platform_driver(sun4i_gpadc_driver);
>> +
>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
>> +MODULE_LICENSE("GPL v2");
>>
> 

-- 
Quentin Schulz, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v6 2/2] iio: adc: add support for Allwinner SoCs ADC
  2016-10-31  9:34       ` Quentin Schulz
@ 2016-11-01 18:09         ` Jonathan Cameron
  -1 siblings, 0 replies; 14+ messages in thread
From: Jonathan Cameron @ 2016-11-01 18:09 UTC (permalink / raw)
  To: Quentin Schulz, jdelvare, linux, knaack.h, lars, pmeerw,
	maxime.ripard, wens, lee.jones
  Cc: thomas.petazzoni, antoine.tenart, linux-kernel, linux-hwmon,
	linux-iio, linux-arm-kernel

On 31/10/16 09:34, Quentin Schulz wrote:
> Hi Jonathan and Lee,
> 
> On 30/10/2016 17:59, Jonathan Cameron wrote:
>> On 15/09/16 13:44, Quentin Schulz wrote:
>>> The Allwinner SoCs all have an ADC that can also act as a touchscreen
>>> controller and a thermal sensor. This patch adds the ADC driver which is
>>> based on the MFD for the same SoCs ADC.
>>>
>>> This also registers the thermal adc channel in the iio map array so
>>> iio_hwmon could use it without modifying the Device Tree. This registers
>>> the driver in the thermal framework.
>>>
>>> This driver probes on three different platform_device_id to take into
>>> account slight differences (registers bit and temperature computation)
>>> between Allwinner SoCs ADCs.
>>>
>>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>>> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>>> Acked-by: Jonathan Cameron <jic23@kernel.org>
>> Hi Lee,
>>
>> As you applied the MFD part of this series, could you pick this up as well?
>>
> 
> Someone reported some bugs on the CHIP (sun5i-r8). I'm investigating that.
> 
> Also, it misses a scale for voltage (not that critical but still).
> 
> And also, Thomas Petazzoni just found a deadlock in sun4i_gpadc_read. If
> a regmap_write fails after getting the mutex, it is never released.
> 
> Is there some sort of deadline you want for a v7 since the mfd patch has
> been merged?
> 
No rush at all.  Doesn't matter as long as we know it's on it's way
sooner or later.  If it falls into the next cycle, that's fine -
I'll just pick it up directly as dependencies will in place by then.

Jonathan
> Thanks,
> Quentin
> 
>> Thanks,
>>
>> Jonathan
>>> ---
>>>
>>> v6:
>>>  - remove useless member (regs) from sun4i_gpadc_dev structure,
>>>  - rename sun4i_gpadc_dev structure to sun4i_gpadc_iio,
>>>  - remove regmap_update_bits used to disable hardware interrupts, it is already
>>>  handled by devm functions,
>>>
>>> v5:
>>>  - correct mail address,
>>>  - correct several typos,
>>>  - move from const to static for sunxi_gpadc_chan_select functions,
>>>  - rename soc_specific struct to gpadc_data,
>>>  - rename soc_specific field to data in sun4i_gpadc_dev,
>>>  - return error code from regmap_write in case of failure in read_raws,
>>>  - share if condition in IIO_CHAN_INFO_RAW case,
>>>  - add comment on why we use parent device for registering in thermal,
>>>  - reordering remove function,
>>>
>>> v4:
>>>  - rename files and variables from sunxi* to sun4i*,
>>>  - shorten sunxi_gpadc_soc_specific structure to soc_specific,
>>>  - factorize sysfs ADC and temp read_raws,
>>>  - use cached values when read_raw times out (except before a first value
>>>    is gotten),
>>>  - remove mutex locks and unlocks from runtime_pm functions,
>>>  - factorize irq initializations,
>>>  - initialize temp_data and fifo_data values to -1 (error value),
>>>  - "impersonate" MFD to register in thermal framework,
>>>  - deactivate hardware interrupts one by one when probe fails or when
>>>    removing driver instead of blindly deactivating all hardware interrupts,
>>>  - selects THERMAL_OF in Kconfig,
>>>
>>> v3:
>>>  - correct wrapping,
>>>  - add comment about thermal sensor inner working,
>>>  - move defines in mfd header,
>>>  - use structure to define SoC specific registers or behaviour,
>>>  - attach this structure to the device according to of_device_id of the
>>>    platform device,
>>>  - use new mutex instead of iio_dev mutex,
>>>  - use atomic flags to avoid race between request_irq and disable_irq in
>>>    probe,
>>>  - switch from processed value to raw, offset and scale values for
>>>    temperature ADC channel,
>>>  - remove faulty sentinel in iio_chan_spec array,
>>>  - add pm_runtime support,
>>>  - register thermal sensor in thermal framework (forgotten since the
>>>    beginning whereas it is present in current sun4i-ts driver),
>>>  - remove useless ret variables to store return value of regmap_reads,
>>>  - move comments on thermal sensor acquisition period in code instead of
>>>    header,
>>>  - adding goto label to unregister iio_map_array when failing to register
>>>    iio_dev,
>>>
>>> v2:
>>>  - add SUNXI_GPADC_ prefixes for defines,
>>>  - correct typo in Kconfig,
>>>  - reorder alphabetically includes, makefile,
>>>  - add license header,
>>>  - fix architecture variations not being handled in interrupt handlers or
>>>    read raw functions,
>>>  - fix unability to return negative values from thermal sensor,
>>>  - add gotos to reduce code repetition,
>>>  - fix irq variable being unsigned int instead of int,
>>>  - remove useless dev_err and dev_info,
>>>  - deactivate all interrupts if probe fails,
>>>  - fix iio_device_register on NULL variable,
>>>  - deactivate ADC in the IP when probe fails or when removing driver,
>>>
>>>  drivers/iio/adc/Kconfig           |  13 +
>>>  drivers/iio/adc/Makefile          |   1 +
>>>  drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
>>>  3 files changed, 536 insertions(+)
>>>  create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
>>>
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index 25378c5..ea36a4f 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -384,6 +384,19 @@ config ROCKCHIP_SARADC
>>>  	  To compile this driver as a module, choose M here: the
>>>  	  module will be called rockchip_saradc.
>>>  
>>> +config SUN4I_GPADC
>>> +	tristate "Support for the Allwinner SoCs GPADC"
>>> +	depends on IIO
>>> +	depends on MFD_SUN4I_GPADC
>>> +	select THERMAL_OF
>>> +	help
>>> +	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
>>> +	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
>>> +	  a touchscreen input and one channel for thermal sensor.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module will be
>>> +	  called sun4i-gpadc-iio.
>>> +
>>>  config TI_ADC081C
>>>  	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>>>  	depends on I2C
>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>> index 38638d4..204372d 100644
>>> --- a/drivers/iio/adc/Makefile
>>> +++ b/drivers/iio/adc/Makefile
>>> @@ -37,6 +37,7 @@ 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
>>>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>>>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>>  obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
>>> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
>>> new file mode 100644
>>> index 0000000..e2c5ba8
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
>>> @@ -0,0 +1,522 @@
>>> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
>>> + *
>>> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
>>> + * Free Software Foundation.
>>> + *
>>> + * The Allwinner SoCs all have an ADC that can also act as a touchscreen
>>> + * controller and a thermal sensor.
>>> + * The thermal sensor works only when the ADC acts as a touchscreen controller
>>> + * and is configured to throw an interrupt every fixed periods of time (let say
>>> + * every X seconds).
>>> + * One would be tempted to disable the IP on the hardware side rather than
>>> + * disabling interrupts to save some power but that resets the internal clock of
>>> + * the IP, resulting in having to wait X seconds every time we want to read the
>>> + * value of the thermal sensor.
>>> + * This is also the reason of using autosuspend in pm_runtime. If there was no
>>> + * autosuspend, the thermal sensor would need X seconds after every
>>> + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
>>> + * thermal sensor to be requested again in a certain time span before it gets
>>> + * shutdown for not being used.
>>> + */
>>> +
>>> +#include <linux/completion.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/io.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/thermal.h>
>>> +
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/driver.h>
>>> +#include <linux/iio/machine.h>
>>> +#include <linux/mfd/sun4i-gpadc.h>
>>> +
>>> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
>>> +{
>>> +	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>> +}
>>> +
>>> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
>>> +{
>>> +	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>> +}
>>> +
>>> +struct gpadc_data {
>>> +	int		temp_offset;
>>> +	int		temp_scale;
>>> +	unsigned int	tp_mode_en;
>>> +	unsigned int	tp_adc_select;
>>> +	unsigned int	(*adc_chan_select)(unsigned int chan);
>>> +};
>>> +
>>> +static const struct gpadc_data sun4i_gpadc_data = {
>>> +	.temp_offset = -1932,
>>> +	.temp_scale = 133,
>>> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>> +	.adc_chan_select = &sun4i_gpadc_chan_select,
>>> +};
>>> +
>>> +static const struct gpadc_data sun5i_gpadc_data = {
>>> +	.temp_offset = -1447,
>>> +	.temp_scale = 100,
>>> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>> +	.adc_chan_select = &sun4i_gpadc_chan_select,
>>> +};
>>> +
>>> +static const struct gpadc_data sun6i_gpadc_data = {
>>> +	.temp_offset = -1623,
>>> +	.temp_scale = 167,
>>> +	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
>>> +	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
>>> +	.adc_chan_select = &sun6i_gpadc_chan_select,
>>> +};
>>> +
>>> +struct sun4i_gpadc_iio {
>>> +	struct iio_dev			*indio_dev;
>>> +	struct completion		completion;
>>> +	int				temp_data;
>>> +	u32				adc_data;
>>> +	struct regmap			*regmap;
>>> +	unsigned int			fifo_data_irq;
>>> +	atomic_t			ignore_fifo_data_irq;
>>> +	unsigned int			temp_data_irq;
>>> +	atomic_t			ignore_temp_data_irq;
>>> +	const struct gpadc_data		*data;
>>> +	/* prevents concurrent reads of temperature and ADC */
>>> +	struct mutex			mutex;
>>> +};
>>> +
>>> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
>>> +	.type = IIO_VOLTAGE,					\
>>> +	.indexed = 1,						\
>>> +	.channel = _channel,					\
>>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
>>> +	.datasheet_name = _name,				\
>>> +}
>>> +
>>> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
>>> +	{
>>> +		.adc_channel_label = "temp_adc",
>>> +		.consumer_dev_name = "iio_hwmon.0",
>>> +	},
>>> +	{ /* sentinel */ },
>>> +};
>>> +
>>> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
>>> +	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>> +	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>> +	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>> +	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>> +	{
>>> +		.type = IIO_TEMP,
>>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>>> +				      BIT(IIO_CHAN_INFO_SCALE) |
>>> +				      BIT(IIO_CHAN_INFO_OFFSET),
>>> +		.datasheet_name = "temp_adc",
>>> +	},
>>> +};
>>> +
>>> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
>>> +			    unsigned int irq)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +	int ret = 0;
>>> +
>>> +	pm_runtime_get_sync(indio_dev->dev.parent);
>>> +	mutex_lock(&info->mutex);
>>> +
>>> +	reinit_completion(&info->completion);
>>> +
>>> +	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
>>> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
>>> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	if (irq == info->fifo_data_irq) {
>>> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>>> +				   info->data->tp_mode_en |
>>> +				   info->data->tp_adc_select |
>>> +				   info->data->adc_chan_select(channel));
>>> +	} else {
>>> +		/*
>>> +		 * The temperature sensor returns valid data only when the ADC
>>> +		 * operates in touchscreen mode.
>>> +		 */
>>> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>>> +				   info->data->tp_mode_en);
>>> +	}
>>> +
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	enable_irq(irq);
>>> +
>>> +	if (!wait_for_completion_timeout(&info->completion,
>>> +					 msecs_to_jiffies(100))) {
>>> +		if ((irq == info->fifo_data_irq && info->adc_data == -1) ||
>>> +		    (irq == info->temp_data_irq && info->temp_data == -1)) {
>>> +			ret = -ETIMEDOUT;
>>> +			goto out;
>>> +		}
>>> +	}
>>> +
>>> +	if (irq == info->fifo_data_irq)
>>> +		*val = info->adc_data;
>>> +	else
>>> +		*val = info->temp_data;
>>> +
>>> +	ret = 0;
>>> +
>>> +out:
>>> +	disable_irq(irq);
>>> +	mutex_unlock(&info->mutex);
>>> +	pm_runtime_mark_last_busy(indio_dev->dev.parent);
>>> +	pm_runtime_put_autosuspend(indio_dev->dev.parent);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
>>> +				int *val)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> +	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> +	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> +	*val = info->data->temp_offset;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> +	*val = info->data->temp_scale;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
>>> +				struct iio_chan_spec const *chan, int *val,
>>> +				int *val2, long mask)
>>> +{
>>> +	int ret;
>>> +
>>> +	switch (mask) {
>>> +	case IIO_CHAN_INFO_OFFSET:
>>> +		ret = sun4i_gpadc_temp_offset(indio_dev, val);
>>> +		if (ret)
>>> +			return ret;
>>> +
>>> +		return IIO_VAL_INT;
>>> +	case IIO_CHAN_INFO_RAW:
>>> +		if (chan->type == IIO_VOLTAGE)
>>> +			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
>>> +						   val);
>>> +		else
>>> +			ret = sun4i_gpadc_temp_read(indio_dev, val);
>>> +
>>> +		if (ret)
>>> +			return ret;
>>> +
>>> +		return IIO_VAL_INT;
>>> +	case IIO_CHAN_INFO_SCALE:
>>> +		ret = sun4i_gpadc_temp_scale(indio_dev, val);
>>> +		if (ret)
>>> +			return ret;
>>> +
>>> +		return IIO_VAL_INT;
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	return -EINVAL;
>>> +}
>>> +
>>> +static const struct iio_info sun4i_gpadc_iio_info = {
>>> +	.read_raw = sun4i_gpadc_read_raw,
>>> +	.driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = dev_id;
>>> +
>>> +	if (atomic_read(&info->ignore_temp_data_irq))
>>> +		return IRQ_HANDLED;
>>> +
>>> +	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
>>> +		complete(&info->completion);
>>> +
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>>> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = dev_id;
>>> +
>>> +	if (atomic_read(&info->ignore_fifo_data_irq))
>>> +		return IRQ_HANDLED;
>>> +
>>> +	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
>>> +		complete(&info->completion);
>>> +
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>> +
>>> +	/* Disable the ADC on IP */
>>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
>>> +	/* Disable temperature sensor on IP */
>>> +	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_runtime_resume(struct device *dev)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>> +
>>> +	/* clkin = 6MHz */
>>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
>>> +		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
>>> +		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
>>> +		     SUN4I_GPADC_CTRL0_T_ACQ(63));
>>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
>>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
>>> +		     SUN4I_GPADC_CTRL3_FILTER_EN |
>>> +		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
>>> +	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~1.3s */
>>> +	regmap_write(info->regmap, SUN4I_GPADC_TPR,
>>> +		     SUN4I_GPADC_TPR_TEMP_ENABLE |
>>> +		     SUN4I_GPADC_TPR_TEMP_PERIOD(1953));
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_get_temp(void *data, int *temp)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
>>> +	int val, scale, offset;
>>> +
>>> +	/* If reading temperature times out, take stored previous value. */
>>> +	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
>>> +		val = info->temp_data;
>>> +	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
>>> +	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
>>> +
>>> +	*temp = (val + offset) * scale;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
>>> +	.get_temp = &sun4i_gpadc_get_temp,
>>> +};
>>> +
>>> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
>>> +	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
>>> +	.runtime_resume = &sun4i_gpadc_runtime_resume,
>>> +};
>>> +
>>> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
>>> +			  irq_handler_t handler, const char *devname,
>>> +			  unsigned int *irq, atomic_t *atomic)
>>> +{
>>> +	int ret;
>>> +	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
>>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
>>> +
>>> +	/*
>>> +	 * Once the interrupt is activated, the IP continuously performs
>>> +	 * conversions thus throws interrupts. The interrupt is activated right
>>> +	 * after being requested but we want to control when these interrupts
>>> +	 * occur thus we disable it right after being requested. However, an
>>> +	 * interrupt might occur between these two instructions and we have to
>>> +	 * make sure that does not happen, by using atomic flags. We set the
>>> +	 * flag before requesting the interrupt and unset it right after
>>> +	 * disabling the interrupt. When an interrupt occurs between these two
>>> +	 * instructions, reading the atomic flag will tell us to ignore the
>>> +	 * interrupt.
>>> +	 */
>>> +	atomic_set(atomic, 1);
>>> +
>>> +	*irq = platform_get_irq_byname(pdev, name);
>>> +	if (*irq < 0) {
>>> +		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
>>> +		return *irq;
>>> +	}
>>> +
>>> +	*irq = regmap_irq_get_virq(mfd_dev->regmap_irqc, *irq);
>>> +	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
>>> +					   devname, info);
>>> +	if (ret < 0) {
>>> +		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
>>> +			name, ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	disable_irq(*irq);
>>> +	atomic_set(atomic, 0);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_probe(struct platform_device *pdev)
>>> +{
>>> +	struct sun4i_gpadc_iio *info;
>>> +	struct iio_dev *indio_dev;
>>> +	int ret;
>>> +	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
>>> +	struct thermal_zone_device *tzd;
>>> +
>>> +	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
>>> +
>>> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>>> +	if (!indio_dev)
>>> +		return -ENOMEM;
>>> +
>>> +	info = iio_priv(indio_dev);
>>> +	platform_set_drvdata(pdev, indio_dev);
>>> +
>>> +	mutex_init(&info->mutex);
>>> +	info->regmap = sun4i_gpadc_dev->regmap;
>>> +	info->indio_dev = indio_dev;
>>> +	info->temp_data = -1;
>>> +	info->adc_data = -1;
>>> +	init_completion(&info->completion);
>>> +	indio_dev->name = dev_name(&pdev->dev);
>>> +	indio_dev->dev.parent = &pdev->dev;
>>> +	indio_dev->dev.of_node = pdev->dev.of_node;
>>> +	indio_dev->info = &sun4i_gpadc_iio_info;
>>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>>> +	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
>>> +	indio_dev->channels = sun4i_gpadc_channels;
>>> +
>>> +	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
>>> +
>>> +	/*
>>> +	 * This driver is a child of an MFD which has a node in the DT but not
>>> +	 * its children. Therefore, the resulting devices of this driver do not
>>> +	 * have an of_node variable.
>>> +	 * However, its parent (the MFD driver) has an of_node variable and
>>> +	 * since devm_thermal_zone_of_sensor_register uses its first argument to
>>> +	 * match the phandle defined in the node of the thermal driver with the
>>> +	 * of_node of the device passed as first argument and the third argument
>>> +	 * to call ops from thermal_zone_of_device_ops, the solution is to use
>>> +	 * the parent device as first argument to match the phandle with its
>>> +	 * of_node, and the device from this driver as third argument to return
>>> +	 * the temperature.
>>> +	 */
>>> +	tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
>>> +						   &sun4i_ts_tz_ops);
>>> +	if (IS_ERR(tzd)) {
>>> +		dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
>>> +			PTR_ERR(tzd));
>>> +		return PTR_ERR(tzd);
>>> +	}
>>> +
>>> +	pm_runtime_set_autosuspend_delay(&pdev->dev,
>>> +					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
>>> +	pm_runtime_use_autosuspend(&pdev->dev);
>>> +	pm_runtime_set_suspended(&pdev->dev);
>>> +	pm_runtime_enable(&pdev->dev);
>>> +
>>> +	ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
>>> +			     sun4i_gpadc_temp_data_irq_handler, "temp_data",
>>> +			     &info->temp_data_irq, &info->ignore_temp_data_irq);
>>> +	if (ret < 0)
>>> +		goto err;
>>> +
>>> +	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
>>> +			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
>>> +			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
>>> +	if (ret < 0)
>>> +		goto err;
>>> +
>>> +	ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
>>> +	if (ret < 0) {
>>> +		dev_err(&pdev->dev, "failed to register iio map array\n");
>>> +		goto err;
>>> +	}
>>> +
>>> +	ret = iio_device_register(indio_dev);
>>> +	if (ret < 0) {
>>> +		dev_err(&pdev->dev, "could not register the device\n");
>>> +		goto err_map;
>>> +	}
>>> +
>>> +	return 0;
>>> +
>>> +err_map:
>>> +	iio_map_array_unregister(indio_dev);
>>> +
>>> +err:
>>> +	pm_runtime_put(&pdev->dev);
>>> +	pm_runtime_disable(&pdev->dev);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int sun4i_gpadc_remove(struct platform_device *pdev)
>>> +{
>>> +	struct sun4i_gpadc_iio *info;
>>> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>> +
>>> +	pm_runtime_put(&pdev->dev);
>>> +	pm_runtime_disable(&pdev->dev);
>>> +	info = iio_priv(indio_dev);
>>> +	iio_map_array_unregister(indio_dev);
>>> +	iio_device_unregister(indio_dev);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct platform_device_id sun4i_gpadc_id[] = {
>>> +	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
>>> +	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
>>> +	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
>>> +	{ /* sentinel */ },
>>> +};
>>> +
>>> +static struct platform_driver sun4i_gpadc_driver = {
>>> +	.driver = {
>>> +		.name = "sun4i-gpadc-iio",
>>> +		.pm = &sun4i_gpadc_pm_ops,
>>> +	},
>>> +	.id_table = sun4i_gpadc_id,
>>> +	.probe = sun4i_gpadc_probe,
>>> +	.remove = sun4i_gpadc_remove,
>>> +};
>>> +
>>> +module_platform_driver(sun4i_gpadc_driver);
>>> +
>>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>>> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
>>> +MODULE_LICENSE("GPL v2");
>>>
>>
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v6 2/2] iio: adc: add support for Allwinner SoCs ADC
@ 2016-11-01 18:09         ` Jonathan Cameron
  0 siblings, 0 replies; 14+ messages in thread
From: Jonathan Cameron @ 2016-11-01 18:09 UTC (permalink / raw)
  To: linux-arm-kernel

On 31/10/16 09:34, Quentin Schulz wrote:
> Hi Jonathan and Lee,
> 
> On 30/10/2016 17:59, Jonathan Cameron wrote:
>> On 15/09/16 13:44, Quentin Schulz wrote:
>>> The Allwinner SoCs all have an ADC that can also act as a touchscreen
>>> controller and a thermal sensor. This patch adds the ADC driver which is
>>> based on the MFD for the same SoCs ADC.
>>>
>>> This also registers the thermal adc channel in the iio map array so
>>> iio_hwmon could use it without modifying the Device Tree. This registers
>>> the driver in the thermal framework.
>>>
>>> This driver probes on three different platform_device_id to take into
>>> account slight differences (registers bit and temperature computation)
>>> between Allwinner SoCs ADCs.
>>>
>>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>>> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>>> Acked-by: Jonathan Cameron <jic23@kernel.org>
>> Hi Lee,
>>
>> As you applied the MFD part of this series, could you pick this up as well?
>>
> 
> Someone reported some bugs on the CHIP (sun5i-r8). I'm investigating that.
> 
> Also, it misses a scale for voltage (not that critical but still).
> 
> And also, Thomas Petazzoni just found a deadlock in sun4i_gpadc_read. If
> a regmap_write fails after getting the mutex, it is never released.
> 
> Is there some sort of deadline you want for a v7 since the mfd patch has
> been merged?
> 
No rush at all.  Doesn't matter as long as we know it's on it's way
sooner or later.  If it falls into the next cycle, that's fine -
I'll just pick it up directly as dependencies will in place by then.

Jonathan
> Thanks,
> Quentin
> 
>> Thanks,
>>
>> Jonathan
>>> ---
>>>
>>> v6:
>>>  - remove useless member (regs) from sun4i_gpadc_dev structure,
>>>  - rename sun4i_gpadc_dev structure to sun4i_gpadc_iio,
>>>  - remove regmap_update_bits used to disable hardware interrupts, it is already
>>>  handled by devm functions,
>>>
>>> v5:
>>>  - correct mail address,
>>>  - correct several typos,
>>>  - move from const to static for sunxi_gpadc_chan_select functions,
>>>  - rename soc_specific struct to gpadc_data,
>>>  - rename soc_specific field to data in sun4i_gpadc_dev,
>>>  - return error code from regmap_write in case of failure in read_raws,
>>>  - share if condition in IIO_CHAN_INFO_RAW case,
>>>  - add comment on why we use parent device for registering in thermal,
>>>  - reordering remove function,
>>>
>>> v4:
>>>  - rename files and variables from sunxi* to sun4i*,
>>>  - shorten sunxi_gpadc_soc_specific structure to soc_specific,
>>>  - factorize sysfs ADC and temp read_raws,
>>>  - use cached values when read_raw times out (except before a first value
>>>    is gotten),
>>>  - remove mutex locks and unlocks from runtime_pm functions,
>>>  - factorize irq initializations,
>>>  - initialize temp_data and fifo_data values to -1 (error value),
>>>  - "impersonate" MFD to register in thermal framework,
>>>  - deactivate hardware interrupts one by one when probe fails or when
>>>    removing driver instead of blindly deactivating all hardware interrupts,
>>>  - selects THERMAL_OF in Kconfig,
>>>
>>> v3:
>>>  - correct wrapping,
>>>  - add comment about thermal sensor inner working,
>>>  - move defines in mfd header,
>>>  - use structure to define SoC specific registers or behaviour,
>>>  - attach this structure to the device according to of_device_id of the
>>>    platform device,
>>>  - use new mutex instead of iio_dev mutex,
>>>  - use atomic flags to avoid race between request_irq and disable_irq in
>>>    probe,
>>>  - switch from processed value to raw, offset and scale values for
>>>    temperature ADC channel,
>>>  - remove faulty sentinel in iio_chan_spec array,
>>>  - add pm_runtime support,
>>>  - register thermal sensor in thermal framework (forgotten since the
>>>    beginning whereas it is present in current sun4i-ts driver),
>>>  - remove useless ret variables to store return value of regmap_reads,
>>>  - move comments on thermal sensor acquisition period in code instead of
>>>    header,
>>>  - adding goto label to unregister iio_map_array when failing to register
>>>    iio_dev,
>>>
>>> v2:
>>>  - add SUNXI_GPADC_ prefixes for defines,
>>>  - correct typo in Kconfig,
>>>  - reorder alphabetically includes, makefile,
>>>  - add license header,
>>>  - fix architecture variations not being handled in interrupt handlers or
>>>    read raw functions,
>>>  - fix unability to return negative values from thermal sensor,
>>>  - add gotos to reduce code repetition,
>>>  - fix irq variable being unsigned int instead of int,
>>>  - remove useless dev_err and dev_info,
>>>  - deactivate all interrupts if probe fails,
>>>  - fix iio_device_register on NULL variable,
>>>  - deactivate ADC in the IP when probe fails or when removing driver,
>>>
>>>  drivers/iio/adc/Kconfig           |  13 +
>>>  drivers/iio/adc/Makefile          |   1 +
>>>  drivers/iio/adc/sun4i-gpadc-iio.c | 522 ++++++++++++++++++++++++++++++++++++++
>>>  3 files changed, 536 insertions(+)
>>>  create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
>>>
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index 25378c5..ea36a4f 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -384,6 +384,19 @@ config ROCKCHIP_SARADC
>>>  	  To compile this driver as a module, choose M here: the
>>>  	  module will be called rockchip_saradc.
>>>  
>>> +config SUN4I_GPADC
>>> +	tristate "Support for the Allwinner SoCs GPADC"
>>> +	depends on IIO
>>> +	depends on MFD_SUN4I_GPADC
>>> +	select THERMAL_OF
>>> +	help
>>> +	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
>>> +	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
>>> +	  a touchscreen input and one channel for thermal sensor.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module will be
>>> +	  called sun4i-gpadc-iio.
>>> +
>>>  config TI_ADC081C
>>>  	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>>>  	depends on I2C
>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>> index 38638d4..204372d 100644
>>> --- a/drivers/iio/adc/Makefile
>>> +++ b/drivers/iio/adc/Makefile
>>> @@ -37,6 +37,7 @@ 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
>>>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>>>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>>  obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
>>> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
>>> new file mode 100644
>>> index 0000000..e2c5ba8
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
>>> @@ -0,0 +1,522 @@
>>> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
>>> + *
>>> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@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 version 2 as published by the
>>> + * Free Software Foundation.
>>> + *
>>> + * The Allwinner SoCs all have an ADC that can also act as a touchscreen
>>> + * controller and a thermal sensor.
>>> + * The thermal sensor works only when the ADC acts as a touchscreen controller
>>> + * and is configured to throw an interrupt every fixed periods of time (let say
>>> + * every X seconds).
>>> + * One would be tempted to disable the IP on the hardware side rather than
>>> + * disabling interrupts to save some power but that resets the internal clock of
>>> + * the IP, resulting in having to wait X seconds every time we want to read the
>>> + * value of the thermal sensor.
>>> + * This is also the reason of using autosuspend in pm_runtime. If there was no
>>> + * autosuspend, the thermal sensor would need X seconds after every
>>> + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
>>> + * thermal sensor to be requested again in a certain time span before it gets
>>> + * shutdown for not being used.
>>> + */
>>> +
>>> +#include <linux/completion.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/io.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/thermal.h>
>>> +
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/driver.h>
>>> +#include <linux/iio/machine.h>
>>> +#include <linux/mfd/sun4i-gpadc.h>
>>> +
>>> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
>>> +{
>>> +	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>> +}
>>> +
>>> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
>>> +{
>>> +	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>> +}
>>> +
>>> +struct gpadc_data {
>>> +	int		temp_offset;
>>> +	int		temp_scale;
>>> +	unsigned int	tp_mode_en;
>>> +	unsigned int	tp_adc_select;
>>> +	unsigned int	(*adc_chan_select)(unsigned int chan);
>>> +};
>>> +
>>> +static const struct gpadc_data sun4i_gpadc_data = {
>>> +	.temp_offset = -1932,
>>> +	.temp_scale = 133,
>>> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>> +	.adc_chan_select = &sun4i_gpadc_chan_select,
>>> +};
>>> +
>>> +static const struct gpadc_data sun5i_gpadc_data = {
>>> +	.temp_offset = -1447,
>>> +	.temp_scale = 100,
>>> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>> +	.adc_chan_select = &sun4i_gpadc_chan_select,
>>> +};
>>> +
>>> +static const struct gpadc_data sun6i_gpadc_data = {
>>> +	.temp_offset = -1623,
>>> +	.temp_scale = 167,
>>> +	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
>>> +	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
>>> +	.adc_chan_select = &sun6i_gpadc_chan_select,
>>> +};
>>> +
>>> +struct sun4i_gpadc_iio {
>>> +	struct iio_dev			*indio_dev;
>>> +	struct completion		completion;
>>> +	int				temp_data;
>>> +	u32				adc_data;
>>> +	struct regmap			*regmap;
>>> +	unsigned int			fifo_data_irq;
>>> +	atomic_t			ignore_fifo_data_irq;
>>> +	unsigned int			temp_data_irq;
>>> +	atomic_t			ignore_temp_data_irq;
>>> +	const struct gpadc_data		*data;
>>> +	/* prevents concurrent reads of temperature and ADC */
>>> +	struct mutex			mutex;
>>> +};
>>> +
>>> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
>>> +	.type = IIO_VOLTAGE,					\
>>> +	.indexed = 1,						\
>>> +	.channel = _channel,					\
>>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
>>> +	.datasheet_name = _name,				\
>>> +}
>>> +
>>> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
>>> +	{
>>> +		.adc_channel_label = "temp_adc",
>>> +		.consumer_dev_name = "iio_hwmon.0",
>>> +	},
>>> +	{ /* sentinel */ },
>>> +};
>>> +
>>> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
>>> +	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>> +	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>> +	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>> +	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>> +	{
>>> +		.type = IIO_TEMP,
>>> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>>> +				      BIT(IIO_CHAN_INFO_SCALE) |
>>> +				      BIT(IIO_CHAN_INFO_OFFSET),
>>> +		.datasheet_name = "temp_adc",
>>> +	},
>>> +};
>>> +
>>> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
>>> +			    unsigned int irq)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +	int ret = 0;
>>> +
>>> +	pm_runtime_get_sync(indio_dev->dev.parent);
>>> +	mutex_lock(&info->mutex);
>>> +
>>> +	reinit_completion(&info->completion);
>>> +
>>> +	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
>>> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
>>> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	if (irq == info->fifo_data_irq) {
>>> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>>> +				   info->data->tp_mode_en |
>>> +				   info->data->tp_adc_select |
>>> +				   info->data->adc_chan_select(channel));
>>> +	} else {
>>> +		/*
>>> +		 * The temperature sensor returns valid data only when the ADC
>>> +		 * operates in touchscreen mode.
>>> +		 */
>>> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>>> +				   info->data->tp_mode_en);
>>> +	}
>>> +
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	enable_irq(irq);
>>> +
>>> +	if (!wait_for_completion_timeout(&info->completion,
>>> +					 msecs_to_jiffies(100))) {
>>> +		if ((irq == info->fifo_data_irq && info->adc_data == -1) ||
>>> +		    (irq == info->temp_data_irq && info->temp_data == -1)) {
>>> +			ret = -ETIMEDOUT;
>>> +			goto out;
>>> +		}
>>> +	}
>>> +
>>> +	if (irq == info->fifo_data_irq)
>>> +		*val = info->adc_data;
>>> +	else
>>> +		*val = info->temp_data;
>>> +
>>> +	ret = 0;
>>> +
>>> +out:
>>> +	disable_irq(irq);
>>> +	mutex_unlock(&info->mutex);
>>> +	pm_runtime_mark_last_busy(indio_dev->dev.parent);
>>> +	pm_runtime_put_autosuspend(indio_dev->dev.parent);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
>>> +				int *val)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> +	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> +	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> +	*val = info->data->temp_offset;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> +	*val = info->data->temp_scale;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
>>> +				struct iio_chan_spec const *chan, int *val,
>>> +				int *val2, long mask)
>>> +{
>>> +	int ret;
>>> +
>>> +	switch (mask) {
>>> +	case IIO_CHAN_INFO_OFFSET:
>>> +		ret = sun4i_gpadc_temp_offset(indio_dev, val);
>>> +		if (ret)
>>> +			return ret;
>>> +
>>> +		return IIO_VAL_INT;
>>> +	case IIO_CHAN_INFO_RAW:
>>> +		if (chan->type == IIO_VOLTAGE)
>>> +			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
>>> +						   val);
>>> +		else
>>> +			ret = sun4i_gpadc_temp_read(indio_dev, val);
>>> +
>>> +		if (ret)
>>> +			return ret;
>>> +
>>> +		return IIO_VAL_INT;
>>> +	case IIO_CHAN_INFO_SCALE:
>>> +		ret = sun4i_gpadc_temp_scale(indio_dev, val);
>>> +		if (ret)
>>> +			return ret;
>>> +
>>> +		return IIO_VAL_INT;
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	return -EINVAL;
>>> +}
>>> +
>>> +static const struct iio_info sun4i_gpadc_iio_info = {
>>> +	.read_raw = sun4i_gpadc_read_raw,
>>> +	.driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = dev_id;
>>> +
>>> +	if (atomic_read(&info->ignore_temp_data_irq))
>>> +		return IRQ_HANDLED;
>>> +
>>> +	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
>>> +		complete(&info->completion);
>>> +
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>>> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = dev_id;
>>> +
>>> +	if (atomic_read(&info->ignore_fifo_data_irq))
>>> +		return IRQ_HANDLED;
>>> +
>>> +	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
>>> +		complete(&info->completion);
>>> +
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>> +
>>> +	/* Disable the ADC on IP */
>>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
>>> +	/* Disable temperature sensor on IP */
>>> +	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_runtime_resume(struct device *dev)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>> +
>>> +	/* clkin = 6MHz */
>>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
>>> +		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
>>> +		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
>>> +		     SUN4I_GPADC_CTRL0_T_ACQ(63));
>>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
>>> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
>>> +		     SUN4I_GPADC_CTRL3_FILTER_EN |
>>> +		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
>>> +	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~1.3s */
>>> +	regmap_write(info->regmap, SUN4I_GPADC_TPR,
>>> +		     SUN4I_GPADC_TPR_TEMP_ENABLE |
>>> +		     SUN4I_GPADC_TPR_TEMP_PERIOD(1953));
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_get_temp(void *data, int *temp)
>>> +{
>>> +	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
>>> +	int val, scale, offset;
>>> +
>>> +	/* If reading temperature times out, take stored previous value. */
>>> +	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
>>> +		val = info->temp_data;
>>> +	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
>>> +	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
>>> +
>>> +	*temp = (val + offset) * scale;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
>>> +	.get_temp = &sun4i_gpadc_get_temp,
>>> +};
>>> +
>>> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
>>> +	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
>>> +	.runtime_resume = &sun4i_gpadc_runtime_resume,
>>> +};
>>> +
>>> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
>>> +			  irq_handler_t handler, const char *devname,
>>> +			  unsigned int *irq, atomic_t *atomic)
>>> +{
>>> +	int ret;
>>> +	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
>>> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
>>> +
>>> +	/*
>>> +	 * Once the interrupt is activated, the IP continuously performs
>>> +	 * conversions thus throws interrupts. The interrupt is activated right
>>> +	 * after being requested but we want to control when these interrupts
>>> +	 * occur thus we disable it right after being requested. However, an
>>> +	 * interrupt might occur between these two instructions and we have to
>>> +	 * make sure that does not happen, by using atomic flags. We set the
>>> +	 * flag before requesting the interrupt and unset it right after
>>> +	 * disabling the interrupt. When an interrupt occurs between these two
>>> +	 * instructions, reading the atomic flag will tell us to ignore the
>>> +	 * interrupt.
>>> +	 */
>>> +	atomic_set(atomic, 1);
>>> +
>>> +	*irq = platform_get_irq_byname(pdev, name);
>>> +	if (*irq < 0) {
>>> +		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
>>> +		return *irq;
>>> +	}
>>> +
>>> +	*irq = regmap_irq_get_virq(mfd_dev->regmap_irqc, *irq);
>>> +	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
>>> +					   devname, info);
>>> +	if (ret < 0) {
>>> +		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
>>> +			name, ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	disable_irq(*irq);
>>> +	atomic_set(atomic, 0);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_probe(struct platform_device *pdev)
>>> +{
>>> +	struct sun4i_gpadc_iio *info;
>>> +	struct iio_dev *indio_dev;
>>> +	int ret;
>>> +	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
>>> +	struct thermal_zone_device *tzd;
>>> +
>>> +	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
>>> +
>>> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>>> +	if (!indio_dev)
>>> +		return -ENOMEM;
>>> +
>>> +	info = iio_priv(indio_dev);
>>> +	platform_set_drvdata(pdev, indio_dev);
>>> +
>>> +	mutex_init(&info->mutex);
>>> +	info->regmap = sun4i_gpadc_dev->regmap;
>>> +	info->indio_dev = indio_dev;
>>> +	info->temp_data = -1;
>>> +	info->adc_data = -1;
>>> +	init_completion(&info->completion);
>>> +	indio_dev->name = dev_name(&pdev->dev);
>>> +	indio_dev->dev.parent = &pdev->dev;
>>> +	indio_dev->dev.of_node = pdev->dev.of_node;
>>> +	indio_dev->info = &sun4i_gpadc_iio_info;
>>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>>> +	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
>>> +	indio_dev->channels = sun4i_gpadc_channels;
>>> +
>>> +	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
>>> +
>>> +	/*
>>> +	 * This driver is a child of an MFD which has a node in the DT but not
>>> +	 * its children. Therefore, the resulting devices of this driver do not
>>> +	 * have an of_node variable.
>>> +	 * However, its parent (the MFD driver) has an of_node variable and
>>> +	 * since devm_thermal_zone_of_sensor_register uses its first argument to
>>> +	 * match the phandle defined in the node of the thermal driver with the
>>> +	 * of_node of the device passed as first argument and the third argument
>>> +	 * to call ops from thermal_zone_of_device_ops, the solution is to use
>>> +	 * the parent device as first argument to match the phandle with its
>>> +	 * of_node, and the device from this driver as third argument to return
>>> +	 * the temperature.
>>> +	 */
>>> +	tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0, info,
>>> +						   &sun4i_ts_tz_ops);
>>> +	if (IS_ERR(tzd)) {
>>> +		dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
>>> +			PTR_ERR(tzd));
>>> +		return PTR_ERR(tzd);
>>> +	}
>>> +
>>> +	pm_runtime_set_autosuspend_delay(&pdev->dev,
>>> +					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
>>> +	pm_runtime_use_autosuspend(&pdev->dev);
>>> +	pm_runtime_set_suspended(&pdev->dev);
>>> +	pm_runtime_enable(&pdev->dev);
>>> +
>>> +	ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
>>> +			     sun4i_gpadc_temp_data_irq_handler, "temp_data",
>>> +			     &info->temp_data_irq, &info->ignore_temp_data_irq);
>>> +	if (ret < 0)
>>> +		goto err;
>>> +
>>> +	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
>>> +			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
>>> +			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
>>> +	if (ret < 0)
>>> +		goto err;
>>> +
>>> +	ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
>>> +	if (ret < 0) {
>>> +		dev_err(&pdev->dev, "failed to register iio map array\n");
>>> +		goto err;
>>> +	}
>>> +
>>> +	ret = iio_device_register(indio_dev);
>>> +	if (ret < 0) {
>>> +		dev_err(&pdev->dev, "could not register the device\n");
>>> +		goto err_map;
>>> +	}
>>> +
>>> +	return 0;
>>> +
>>> +err_map:
>>> +	iio_map_array_unregister(indio_dev);
>>> +
>>> +err:
>>> +	pm_runtime_put(&pdev->dev);
>>> +	pm_runtime_disable(&pdev->dev);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int sun4i_gpadc_remove(struct platform_device *pdev)
>>> +{
>>> +	struct sun4i_gpadc_iio *info;
>>> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>> +
>>> +	pm_runtime_put(&pdev->dev);
>>> +	pm_runtime_disable(&pdev->dev);
>>> +	info = iio_priv(indio_dev);
>>> +	iio_map_array_unregister(indio_dev);
>>> +	iio_device_unregister(indio_dev);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct platform_device_id sun4i_gpadc_id[] = {
>>> +	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
>>> +	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
>>> +	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
>>> +	{ /* sentinel */ },
>>> +};
>>> +
>>> +static struct platform_driver sun4i_gpadc_driver = {
>>> +	.driver = {
>>> +		.name = "sun4i-gpadc-iio",
>>> +		.pm = &sun4i_gpadc_pm_ops,
>>> +	},
>>> +	.id_table = sun4i_gpadc_id,
>>> +	.probe = sun4i_gpadc_probe,
>>> +	.remove = sun4i_gpadc_remove,
>>> +};
>>> +
>>> +module_platform_driver(sun4i_gpadc_driver);
>>> +
>>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>>> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
>>> +MODULE_LICENSE("GPL v2");
>>>
>>
> 

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2016-11-01 18:10 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-09-15 12:44 [PATCH v6 0/2] add support for Allwinner SoCs ADC Quentin Schulz
2016-09-15 12:44 ` Quentin Schulz
2016-09-15 12:44 ` [PATCH v6 1/2] mfd: " Quentin Schulz
2016-09-15 12:44   ` Quentin Schulz
2016-10-26  9:18   ` Lee Jones
2016-10-26  9:18     ` Lee Jones
2016-09-15 12:44 ` [PATCH v6 2/2] iio: adc: " Quentin Schulz
2016-09-15 12:44   ` Quentin Schulz
2016-10-30 16:59   ` Jonathan Cameron
2016-10-30 16:59     ` Jonathan Cameron
2016-10-31  9:34     ` Quentin Schulz
2016-10-31  9:34       ` Quentin Schulz
2016-11-01 18:09       ` Jonathan Cameron
2016-11-01 18:09         ` Jonathan Cameron

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.