[PATCH 2/3] iio: adc: add support for Allwinner SoCs ADC
Guenter Roeck
linux at roeck-us.net
Mon Jul 4 09:29:09 PDT 2016
On 07/04/2016 12:26 AM, Quentin Schulz wrote:
> On 03/07/2016 17:43, Guenter Roeck wrote:
>> On 07/03/2016 04:54 AM, Jonathan Cameron wrote:
>>> On 28/06/16 09:18, 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 driver probes on three different platform_device_id to take into
>>>> account slight differences between Allwinner SoCs ADCs.
>>>>
>>>> Signed-off-by: Quentin Schulz <quentin.schulz at free-electrons.com>
>>> Hi Quentin.
>>>
>>> I'm a bit in two minds about some of this. That temperature sensor is
>>> so obviously meant for hwmon purposes, I'm tempted to suggest it might
>>> actually make sense to put it directly in hwmon rather than using the
>>> bridge. That obviously makes it less flexible in some ways (i.e. for
>>> use within the thermal subsystem at some point).
>>>
>>> Guenter, what do you think?
>>>
>>
>> With the upcoming new hwmon API, thermal registration is handled in the
>> hwmon core, so that should not be an issue. Besides, other hwmon sensors
>> already register with the thermal subsystem as well.
>>
>> This is difficult to evaluate without datasheet; I am not sure if
>> the chip supports limits or trip points. If it supports trip points,
>> thermal may be a better target.
>>
>> Overall it does look like the temperature sensor would warrant
>> a separate driver. Only question is thermal or hwmon.
>>
>> Guenter
>
> Actually, the publicly available documentation for the temperature
> sensor is almost inexistent. We only have the registers for it. For most
> of the work on temperature sensor, it has been taken from the current
> driver (sun4i-ts in input/touschreen) from Hans de Goede.
>
Does this mean that the temperature data will be exported twice, once through
iio and once through the touchscreen driver ?
Guenter
> You can find the documentation here:
> http://dl.linux-sunxi.org/A10/A10%20User%20manual%20V1.50.pdf
> http://dl.linux-sunxi.org/A13/A13%20User%20Manual%20V1.30.pdf
> http://dl.linux-sunxi.org/A31/A31%20User%20Manual%20V1.20.pdf
>
> The ADC is either called TP controller, Touch Panel controller or GPADC.
> The temperature sensor's registers are only defined in the User Manual
> of the A10 (first link).
>
> Nothing to be found for limits or trip points I think.
>
> Quentin
>
>>> I'm guessing detailed docs for this part aren't avaiable publicly? :(
>>>
>>> So the rest of my comments are kind of predicated on me having roughtly
>>> understood how this device works from the structure of the driver.
>>>
>>> The temperature sensor is really effectively as separate ADC?
>>> The main interest in this is for key detection?
>>>
>>> Anyhow, if the data flow for the temperatures sensor is not synced with
>>> the other ADC channels, adding buffered (pushed) output from the
>>> driver in
>>> future will be fiddly and with a 250Hz device you'll probably want it.
>>> Basically IIO buffered supports assumes each iio device will sample data
>>> at a particular frequency. If channels are not synchronized in that
>>> fashion
>>> then you have to register multiple devices or only pick a subset of
>>> channels
>>> to export.
>>>
>>> For the key detection you have already observed that IIO needs some
>>> additions to be able to have consumers of what we term 'events' e.g.
>>> threshold
>>> interrupts.
>>>
>>> Looking at the lradc-keys driver in tree, it looks like we only really
>>> have
>>> really simple threshold interrups - configured to detect a very low
>>> voltage?
>>> + only one per channel.
>>>
>>> So not too nasty a case, but you are right some work is needed in IIO as
>>> we simply don't have a means of passing these on as yet or configuring
>>> them
>>> from in kernel consumers.
>>> If we take the easy route and don't demux incoming events then it
>>> shouldn't
>>> be too hard to add (demux can follow later). Hence any client device
>>> can try
>>> to enable events it wants, but may get events that other client
>>> devices wanted
>>> as well.
>>>
>>> Config interface should be much the same as the write support for
>>> channels.
>>> Data flow marginally harder, but pretty much a list of callbacks within
>>> iio_push_event.
>>>
>>> Not trivial, but not too tricky either.
>>>
>>> The events subsystem has a few 'limitations' we need to address long term
>>> but as this is in kernel interface only, we can do this now and fix stuff
>>> up in future without any ABI breakage. (limitations are things like only
>>> one event of a given type and direction per channel - main challenge on
>>> that is finding a way of doing it without abi breakage).
>>>
>>> Anyhow, sounds fun - wish I had the time to do it myself!
>>>
>>> Otherwise, your remove is never going to work as indio_dev is always
>>> NULL.
>>>
>>> Jonathan
>>>
>>>> ---
>>>> drivers/iio/adc/Kconfig | 12 ++
>>>> drivers/iio/adc/Makefile | 1 +
>>>> drivers/iio/adc/sunxi-gpadc-iio.c | 371
>>>> ++++++++++++++++++++++++++++++++++++++
>>>> 3 files changed, 384 insertions(+)
>>>> create mode 100644 drivers/iio/adc/sunxi-gpadc-iio.c
>>>>
>>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>>> index 82c718c..b7b566a 100644
>>>> --- a/drivers/iio/adc/Kconfig
>>>> +++ b/drivers/iio/adc/Kconfig
>>>> @@ -328,6 +328,18 @@ config NAU7802
>>>> To compile this driver as a module, choose M here: the
>>>> module will be called nau7802.
>>>>
>>>> +config SUNXI_ADC
>>>> + tristate "ADC driver for sunxi platforms"
>>>> + depends on IIO
>>>> + depends on MFD_SUNXI_ADC
>>>> + help
>>>> + Say yes here to build support for Allwinner SoCs (A10, A13 and
>>>> A31)
>>>> + SoCs ADC. 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 sunxi-gpadc-iio.
>>>> +
>>>> config PALMAS_GPADC
>>>> tristate "TI Palmas General Purpose ADC"
>>>> depends on MFD_PALMAS
>>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>>> index 0cb7921..2996a5b 100644
>>>> --- a/drivers/iio/adc/Makefile
>>>> +++ b/drivers/iio/adc/Makefile
>>>> @@ -32,6 +32,7 @@ obj-$(CONFIG_MCP3422) += mcp3422.o
>>>> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
>>>> obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o
>>>> obj-$(CONFIG_NAU7802) += nau7802.o
>>>> +obj-$(CONFIG_SUNXI_ADC) += sunxi-gpadc-iio.o
>>>> obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>>>> obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>>>> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>>>> diff --git a/drivers/iio/adc/sunxi-gpadc-iio.c
>>>> b/drivers/iio/adc/sunxi-gpadc-iio.c
>>>> new file mode 100644
>>>> index 0000000..5840f43
>>>> --- /dev/null
>>>> +++ b/drivers/iio/adc/sunxi-gpadc-iio.c
>>>> @@ -0,0 +1,371 @@
>>>> +#include <linux/module.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/completion.h>
>>>> +#include <linux/iio/iio.h>
>>>> +#include <linux/iio/machine.h>
>>>> +#include <linux/iio/driver.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/mfd/sunxi-gpadc-mfd.h>
>>>> +#include <linux/regmap.h>
>>>> +
>>> I'm fussy about this: Please prefix all defines as some of these
>>> are generic enough they may well end up defined in an included header
>>> sometime in the future.
>>>
>>>> +#define TP_CTRL0 0x00
>>>> +#define TP_CTRL1 0x04
>>>> +#define TP_CTRL2 0x08
>>>> +#define TP_CTRL3 0x0c
>>>> +#define TP_TPR 0x18
>>>> +#define TP_CDAT 0x1c
>>>> +#define TEMP_DATA 0x20
>>>> +#define TP_DATA 0x24
>>>> +
>>>> +/* TP_CTRL0 bits */
>>>> +#define ADC_FIRST_DLY(x) ((x) << 24) /* 8 bits */
>>>> +#define ADC_FIRST_DLY_MODE BIT(23)
>>>> +#define ADC_CLK_SELECT BIT(22)
>>>> +#define ADC_CLK_DIVIDER(x) ((x) << 20) /* 2 bits */
>>>> +#define FS_DIV(x) ((x) << 16) /* 4 bits */
>>>> +#define T_ACQ(x) ((x) << 0) /* 16 bits*/
>>>> +
>>>> +/* TP_CTRL1 bits */
>>>> +#define STYLUS_UP_DEBOUNCE(x) ((x) << 12) /* 8 bits */
>>>> +#define STYLUS_UP_DEBOUNCE_EN BIT(9)
>>>> +#define TOUCH_PAN_CALI_EN BIT(6)
>>>> +#define TP_DUAL_EN BIT(5)
>>>> +#define TP_MODE_EN BIT(4)
>>>> +#define TP_ADC_SELECT BIT(3)
>>>> +#define ADC_CHAN_SELECT(x) ((x) << 0) /* 3 bits */
>>>> +
>>>> +/* TP_CTRL1 bits for sun6i SOCs */
>>>> +#define SUN6I_TOUCH_PAN_CALI_EN BIT(7)
>>>> +#define SUN6I_TP_DUAL_EN BIT(6)
>>>> +#define SUN6I_TP_MODE_EN BIT(5)
>>>> +#define SUN6I_TP_ADC_SELECT BIT(4)
>>>> +#define SUN6I_ADC_CHAN_SELECT(x) BIT(x) /* 4 bits */
>>>> +
>>>> +/* TP_CTRL2 bits */
>>>> +#define TP_SENSITIVE_ADJUST(x) ((x) << 28) /* 4 bits */
>>>> +#define TP_MODE_SELECT(x) ((x) << 26) /* 2 bits */
>>>> +#define PRE_MEA_EN BIT(24)
>>>> +#define PRE_MEA_THRE_CNT(x) ((x) << 0) /* 24 bits*/
>>>> +
>>>> +/* TP_CTRL3 bits */
>>>> +#define FILTER_EN BIT(2)
>>>> +#define FILTER_TYPE(x) ((x) << 0) /* 2 bits */
>>>> +
>>>> +/* TP_INT_FIFOC irq and fifo mask / control bits */
>>>> +#define TEMP_IRQ_EN BIT(18)
>>>> +#define TP_OVERRUN_IRQ_EN BIT(17)
>>>> +#define TP_DATA_IRQ_EN BIT(16)
>>>> +#define TP_DATA_XY_CHANGE BIT(13)
>>>> +#define TP_FIFO_TRIG_LEVEL(x) ((x) << 8) /* 5 bits */
>>>> +#define TP_DATA_DRQ_EN BIT(7)
>>>> +#define TP_FIFO_FLUSH BIT(4)
>>>> +#define TP_UP_IRQ_EN BIT(1)
>>>> +#define TP_DOWN_IRQ_EN BIT(0)
>>>> +
>>>> +/* TP_INT_FIFOS irq and fifo status bits */
>>>> +#define TEMP_DATA_PENDING BIT(18)
>>>> +#define FIFO_OVERRUN_PENDING BIT(17)
>>>> +#define FIFO_DATA_PENDING BIT(16)
>>>> +#define TP_IDLE_FLG BIT(2)
>>>> +#define TP_UP_PENDING BIT(1)
>>>> +#define TP_DOWN_PENDING BIT(0)
>>>> +
>>>> +/* TP_TPR bits */
>>>> +#define TEMP_ENABLE(x) ((x) << 16)
>>>> +#define TEMP_PERIOD(x) ((x) << 0) /*t = x * 256 * 16 /
>>>> clkin*/
>>>> +
>>>> +#define ARCH_SUN4I BIT(0)
>>>> +#define ARCH_SUN5I BIT(1)
>>>> +#define ARCH_SUN6I BIT(2)
>>>> +
>>>> +struct sunxi_gpadc_dev {
>>>> + void __iomem *regs;
>>>> + struct completion completion;
>>>> + int temp_data;
>>>> + u32 adc_data;
>>>> + struct regmap *regmap;
>>>> + unsigned int fifo_data_irq;
>>>> + unsigned int temp_data_irq;
>>>> + unsigned int flags;
>>>> +};
>>>> +
>>>> +#define ADC_CHANNEL(_channel, _name) { \
>>>> + .type = IIO_VOLTAGE, \
>>>> + .indexed = 1, \
>>>> + .channel = _channel, \
>>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
>>>> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>>>> + .datasheet_name = _name, \
>>>> +}
>>>> +
>>>> +static struct iio_map sunxi_gpadc_hwmon_maps[] = {
>>>> + {
>>>> + .adc_channel_label = "temp_adc",
>>>> + .consumer_dev_name = "iio_hwmon.0",
>>>> + },
>>>> + {},
>>>> +};
>>>> +
>>>> +static const struct iio_chan_spec sunxi_gpadc_channels[] = {
>>>> + ADC_CHANNEL(0, "adc_chan0"),
>>>> + ADC_CHANNEL(1, "adc_chan1"),
>>>> + ADC_CHANNEL(2, "adc_chan2"),
>>>> + ADC_CHANNEL(3, "adc_chan3"),
>>>> + {
>>>> + .type = IIO_TEMP,
>>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
>>>> + .datasheet_name = "temp_adc",
>>>> + },
>>>> +};
>>>> +
>>>> +static int sunxi_gpadc_adc_read(struct iio_dev *indio_dev, int channel)
>>>> +{
>>>> + struct sunxi_gpadc_dev *info = iio_priv(indio_dev);
>>>> + int val = 0;
>>>> +
>>>> + mutex_lock(&indio_dev->mlock);
>>>> +
>>>> + reinit_completion(&info->completion);
>>>> + regmap_write(info->regmap, TP_CTRL1, SUN6I_TP_MODE_EN |
>>>> + SUN6I_TP_ADC_SELECT |
>>>> + SUN6I_ADC_CHAN_SELECT(channel));
>>>> + regmap_write(info->regmap, TP_INT_FIFOC, TP_FIFO_TRIG_LEVEL(1) |
>>>> + TP_FIFO_FLUSH);
>>>> + enable_irq(info->fifo_data_irq);
>>>> +
>>>> + if (!wait_for_completion_timeout(&info->completion,
>>>> + msecs_to_jiffies(100))) {
>>>> + disable_irq(info->fifo_data_irq);
>>>> + mutex_unlock(&indio_dev->mlock);
>>>> + return -ETIMEDOUT;
>>>> + }
>>>> +
>>>> + val = info->adc_data;
>>>> + disable_irq(info->fifo_data_irq);
>>>> + mutex_unlock(&indio_dev->mlock);
>>>> +
>>>> + return val;
>>>> +}
>>>> +
>>>> +static int sunxi_gpadc_temp_read(struct iio_dev *indio_dev)
>>>> +{
>>>> + struct sunxi_gpadc_dev *info = iio_priv(indio_dev);
>>>> + int val = 0;
>>>> +
>>>> + mutex_lock(&indio_dev->mlock);
>>>> +
>>>> + reinit_completion(&info->completion);
>>>> +
>>>> + regmap_write(info->regmap, TP_INT_FIFOC, TP_FIFO_TRIG_LEVEL(1) |
>>>> + TP_FIFO_FLUSH);
>>>> + regmap_write(info->regmap, TP_CTRL1, SUN6I_TP_MODE_EN);
>>>> + enable_irq(info->temp_data_irq);
>>>> +
>>>> + if (!wait_for_completion_timeout(&info->completion,
>>>> + msecs_to_jiffies(100))) {
>>>> + disable_irq(info->temp_data_irq);
>>>> + mutex_unlock(&indio_dev->mlock);
>>>> + return -ETIMEDOUT;
>>>> + }
>>>> +
>>>> + if (info->flags & ARCH_SUN4I)
>>>> + val = info->temp_data * 133 - 257000;
>>>> + else if (info->flags & ARCH_SUN5I)
>>>> + val = info->temp_data * 100 - 144700;
>>>> + else if (info->flags & ARCH_SUN6I)
>>>> + val = info->temp_data * 167 - 271000;
>>>> +
>>>> + disable_irq(info->temp_data_irq);
>>>> + mutex_unlock(&indio_dev->mlock);
>>>> + return val;
>>>> +}
>>>> +
>>>> +static int sunxi_gpadc_read_raw(struct iio_dev *indio_dev,
>>>> + struct iio_chan_spec const *chan,
>>>> + int *val, int *val2, long mask)
>>>> +{
>>>> + switch (mask) {
>>>> + case IIO_CHAN_INFO_PROCESSED:
>>>> + *val = sunxi_gpadc_temp_read(indio_dev);
>>>> + if (*val < 0)
>>>> + return *val;
>>>> +
>>>> + return IIO_VAL_INT;
>>>> + case IIO_CHAN_INFO_RAW:
>>>> + *val = sunxi_gpadc_adc_read(indio_dev, chan->channel);
>>>> + if (*val < 0)
>>>> + return *val;
>>>> +
>>>> + return IIO_VAL_INT;
>>>> + default:
>>>> + break;
>>>> + }
>>>> +
>>>> + return -EINVAL;
>>>> +}
>>>> +
>>>> +static const struct iio_info sunxi_gpadc_iio_info = {
>>>> + .read_raw = sunxi_gpadc_read_raw,
>>>> + .driver_module = THIS_MODULE,
>>>> +};
>>>> +
>>>> +static irqreturn_t sunxi_gpadc_temp_data_irq_handler(int irq, void
>>>> *dev_id)
>>>> +{
>>>> + struct sunxi_gpadc_dev *info = dev_id;
>>>> + int ret;
>>>> +
>>>> + ret = regmap_read(info->regmap, TEMP_DATA, &info->temp_data);
>>>> + if (ret == 0)
>>>> + complete(&info->completion);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>> Interesting that it's a separate data flow for the temperature sensor.
>>> That means that if you add buffered (pushed data) support in future
>>> either
>>> you'll need to split the device in two or not allow the temperature
>>> channel
>>> to go through the buffer interface.
>>>> +
>>>> +static irqreturn_t sunxi_gpadc_fifo_data_irq_handler(int irq, void
>>>> *dev_id)
>>>> +{
>>>> + struct sunxi_gpadc_dev *info = dev_id;
>>>> + int ret;
>>>> +
>>>> + ret = regmap_read(info->regmap, TP_DATA, &info->adc_data);
>>>> + if (ret == 0)
>>>> + complete(&info->completion);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static int sunxi_gpadc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct sunxi_gpadc_dev *info = NULL;
>>>> + struct iio_dev *indio_dev = NULL;
>>>> + int ret = 0;
>>>> + unsigned int irq;
>>>> + struct sunxi_gpadc_mfd_dev *sunxi_gpadc_mfd_dev;
>>>> +
>>>> + sunxi_gpadc_mfd_dev = dev_get_drvdata(pdev->dev.parent);
>>>> +
>>>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>>>> + if (!indio_dev) {
>>>> + dev_err(&pdev->dev, "failed to allocate iio device.\n");
>>>> + return -ENOMEM;
>>>> + }
>>>> + info = iio_priv(indio_dev);
>>>> +
>>>> + info->regmap = sunxi_gpadc_mfd_dev->regmap;
>>>> + 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 = &sunxi_gpadc_iio_info;
>>>> + indio_dev->modes = INDIO_DIRECT_MODE;
>>>> + indio_dev->num_channels = ARRAY_SIZE(sunxi_gpadc_channels);
>>>> + indio_dev->channels = sunxi_gpadc_channels;
>>>> +
>>>> + info->flags = platform_get_device_id(pdev)->driver_data;
>>>> +
>>>> + regmap_write(info->regmap, TP_CTRL0, ADC_CLK_DIVIDER(2) |
>>>> + FS_DIV(7) |
>>>> + T_ACQ(63));
>>>> + regmap_write(info->regmap, TP_CTRL1, SUN6I_TP_MODE_EN);
>>>> + regmap_write(info->regmap, TP_CTRL3, FILTER_EN | FILTER_TYPE(1));
>>>> + regmap_write(info->regmap, TP_TPR, TEMP_ENABLE(1) |
>>>> TEMP_PERIOD(1953));
>>>> +
>>>> + irq = platform_get_irq_byname(pdev, "TEMP_DATA_PENDING");
>>>> + if (irq < 0) {
>>>> + dev_err(&pdev->dev,
>>>> + "no TEMP_DATA_PENDING interrupt registered\n");
>>>> + return irq;
>>>> + }
>>>> +
>>>> + irq = regmap_irq_get_virq(sunxi_gpadc_mfd_dev->regmap_irqc, irq);
>>>> + ret = devm_request_any_context_irq(&pdev->dev, irq,
>>>> + sunxi_gpadc_temp_data_irq_handler,
>>>> + 0, "temp_data", info);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev,
>>>> + "could not request TEMP_DATA_PENDING interrupt: %d\n",
>>>> + ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + info->temp_data_irq = irq;
>>>> + disable_irq(irq);
>>>> +
>>>> + irq = platform_get_irq_byname(pdev, "FIFO_DATA_PENDING");
>>> hohum. A fifo? This part is getting more interesting ;) I'll have to
>>> dig out the datasheet at some point (if public).
>>>> + if (irq < 0) {
>>>> + dev_err(&pdev->dev,
>>>> + "no FIFO_DATA_PENDING interrupt registered\n");
>>>> + return irq;
>>>> + }
>>>> +
>>>> + irq = regmap_irq_get_virq(sunxi_gpadc_mfd_dev->regmap_irqc, irq);
>>>> + ret = devm_request_any_context_irq(&pdev->dev, irq,
>>>> + sunxi_gpadc_fifo_data_irq_handler,
>>>> + 0, "fifo_data", info);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev,
>>>> + "could not request FIFO_DATA_PENDING interrupt: %d\n",
>>>> + ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + info->fifo_data_irq = irq;
>>>> + disable_irq(irq);
>>>> +
>>>> + ret = iio_map_array_register(indio_dev, sunxi_gpadc_hwmon_maps);
>>> As mentioned for previous patch I think this should be described
>>> externally.
>>> Chances are that some of those other adc channels are also going to be
>>> in reality used for hwmon anyway so doing it in the device tree will give
>>> you more flexibility.
>>>
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev, "failed to register iio map array\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + platform_set_drvdata(pdev, indio_dev);
>>>> +
>>>> + ret = iio_device_register(indio_dev);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev, "could not register the device\n");
>>>> + iio_map_array_unregister(indio_dev);
>>>> + return ret;
>>>> + }
>>>> +
>>> This is kind of self evident when the device turns up so I'd not bother
>>> cluttering up the logs with it as no additional information is given.
>>>> + dev_info(&pdev->dev, "successfully loaded\n");
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int sunxi_gpadc_remove(struct platform_device *pdev)
>>>> +{
>>>> + struct iio_dev *indio_dev = NULL;
>>> ? If it's null the below isn't going to work as intended.
>>> Missing a platform_get_drvdata which I'd just roll into the above
>>> local variable definition.
>>>
>>>> + struct sunxi_gpadc_dev *info = NULL;
>>>> +
>>>> + iio_device_unregister(indio_dev);
>>>> + iio_map_array_unregister(indio_dev);
>>>> + info = iio_priv(indio_dev);
>>> I'd roll this into the local variable declaration above
>>> (it's only a trivial bit of pointer arithemetic after all.
>>> struct sunxi_gpadc_dev *info = iio_priv(indio_dev);
>>>
>>>> + regmap_write(info->regmap, TP_INT_FIFOC, 0);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct platform_device_id sunxi_gpadc_id[] = {
>>>> + { "sun4i-a10-gpadc-iio", ARCH_SUN4I },
>>>> + { "sun5i-a13-gpadc-iio", ARCH_SUN5I },
>>>> + { "sun6i-a31-gpadc-iio", ARCH_SUN6I },
>>>> + { /*sentinel*/ },
>>>> +};
>>>> +
>>>> +static struct platform_driver sunxi_gpadc_driver = {
>>>> + .driver = {
>>>> + .name = "sunxi-gpadc-iio",
>>>> + },
>>>> + .id_table = sunxi_gpadc_id,
>>>> + .probe = sunxi_gpadc_probe,
>>>> + .remove = sunxi_gpadc_remove,
>>>> +};
>>>> +
>>>> +module_platform_driver(sunxi_gpadc_driver);
>>>> +
>>>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>>>> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz at free-electrons.com>");
>>>> +MODULE_LICENSE("GPL v2");
>>>>
>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in
>>> the body of a message to majordomo at vger.kernel.org
>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>>
>>
>
More information about the linux-arm-kernel
mailing list