root/drivers/iio/adc/ingenic-adc.c
// SPDX-License-Identifier: GPL-2.0
/*
 * ADC driver for the Ingenic JZ47xx SoCs
 * Copyright (c) 2019 Artur Rojek <contact@artur-rojek.eu>
 *
 * based on drivers/mfd/jz4740-adc.c
 */

#include <dt-bindings/iio/adc/ingenic,adc.h>
#include <linux/clk.h>
#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>

#define JZ_ADC_REG_ENABLE               0x00
#define JZ_ADC_REG_CFG                  0x04
#define JZ_ADC_REG_CTRL                 0x08
#define JZ_ADC_REG_STATUS               0x0c
#define JZ_ADC_REG_ADSAME               0x10
#define JZ_ADC_REG_ADWAIT               0x14
#define JZ_ADC_REG_ADTCH                0x18
#define JZ_ADC_REG_ADBDAT               0x1c
#define JZ_ADC_REG_ADSDAT               0x20
#define JZ_ADC_REG_ADCMD                0x24
#define JZ_ADC_REG_ADCLK                0x28

#define JZ_ADC_REG_ENABLE_PD            BIT(7)
#define JZ_ADC_REG_CFG_AUX_MD           (BIT(0) | BIT(1))
#define JZ_ADC_REG_CFG_BAT_MD           BIT(4)
#define JZ_ADC_REG_CFG_SAMPLE_NUM(n)    ((n) << 10)
#define JZ_ADC_REG_CFG_PULL_UP(n)       ((n) << 16)
#define JZ_ADC_REG_CFG_CMD_SEL          BIT(22)
#define JZ_ADC_REG_CFG_VBAT_SEL         BIT(30)
#define JZ_ADC_REG_CFG_TOUCH_OPS_MASK   (BIT(31) | GENMASK(23, 10))
#define JZ_ADC_REG_ADCLK_CLKDIV_LSB     0
#define JZ4725B_ADC_REG_ADCLK_CLKDIV10US_LSB    16
#define JZ4770_ADC_REG_ADCLK_CLKDIV10US_LSB     8
#define JZ4770_ADC_REG_ADCLK_CLKDIVMS_LSB       16

#define JZ_ADC_REG_ADCMD_YNADC          BIT(7)
#define JZ_ADC_REG_ADCMD_YPADC          BIT(8)
#define JZ_ADC_REG_ADCMD_XNADC          BIT(9)
#define JZ_ADC_REG_ADCMD_XPADC          BIT(10)
#define JZ_ADC_REG_ADCMD_VREFPYP        BIT(11)
#define JZ_ADC_REG_ADCMD_VREFPXP        BIT(12)
#define JZ_ADC_REG_ADCMD_VREFPXN        BIT(13)
#define JZ_ADC_REG_ADCMD_VREFPAUX       BIT(14)
#define JZ_ADC_REG_ADCMD_VREFPVDD33     BIT(15)
#define JZ_ADC_REG_ADCMD_VREFNYN        BIT(16)
#define JZ_ADC_REG_ADCMD_VREFNXP        BIT(17)
#define JZ_ADC_REG_ADCMD_VREFNXN        BIT(18)
#define JZ_ADC_REG_ADCMD_VREFAUX        BIT(19)
#define JZ_ADC_REG_ADCMD_YNGRU          BIT(20)
#define JZ_ADC_REG_ADCMD_XNGRU          BIT(21)
#define JZ_ADC_REG_ADCMD_XPGRU          BIT(22)
#define JZ_ADC_REG_ADCMD_YPSUP          BIT(23)
#define JZ_ADC_REG_ADCMD_XNSUP          BIT(24)
#define JZ_ADC_REG_ADCMD_XPSUP          BIT(25)

#define JZ_ADC_AUX_VREF                         3300
#define JZ_ADC_AUX_VREF_BITS                    12
#define JZ_ADC_BATTERY_LOW_VREF                 2500
#define JZ_ADC_BATTERY_LOW_VREF_BITS            12
#define JZ4725B_ADC_BATTERY_HIGH_VREF           7500
#define JZ4725B_ADC_BATTERY_HIGH_VREF_BITS      10
#define JZ4740_ADC_BATTERY_HIGH_VREF            (7500 * 0.986)
#define JZ4740_ADC_BATTERY_HIGH_VREF_BITS       12
#define JZ4760_ADC_BATTERY_VREF                 2500
#define JZ4770_ADC_BATTERY_VREF                 1200
#define JZ4770_ADC_BATTERY_VREF_BITS            12

#define JZ_ADC_IRQ_AUX                  BIT(0)
#define JZ_ADC_IRQ_BATTERY              BIT(1)
#define JZ_ADC_IRQ_TOUCH                BIT(2)
#define JZ_ADC_IRQ_PEN_DOWN             BIT(3)
#define JZ_ADC_IRQ_PEN_UP               BIT(4)
#define JZ_ADC_IRQ_PEN_DOWN_SLEEP       BIT(5)
#define JZ_ADC_IRQ_SLEEP                BIT(7)

struct ingenic_adc;

struct ingenic_adc_soc_data {
        unsigned int battery_high_vref;
        unsigned int battery_high_vref_bits;
        const int *battery_raw_avail;
        size_t battery_raw_avail_size;
        const int *battery_scale_avail;
        size_t battery_scale_avail_size;
        unsigned int battery_vref_mode: 1;
        unsigned int has_aux_md: 1;
        const struct iio_chan_spec *channels;
        unsigned int num_channels;
        int (*init_clk_div)(struct device *dev, struct ingenic_adc *adc);
};

struct ingenic_adc {
        void __iomem *base;
        struct clk *clk;
        struct mutex lock;
        struct mutex aux_lock;
        const struct ingenic_adc_soc_data *soc_data;
        bool low_vref_mode;
};

static void ingenic_adc_set_adcmd(struct iio_dev *iio_dev, unsigned long mask)
{
        struct ingenic_adc *adc = iio_priv(iio_dev);

        mutex_lock(&adc->lock);

        /* Init ADCMD */
        readl(adc->base + JZ_ADC_REG_ADCMD);

        if (mask & 0x3) {
                /* Second channel (INGENIC_ADC_TOUCH_YP): sample YP vs. GND */
                writel(JZ_ADC_REG_ADCMD_XNGRU
                       | JZ_ADC_REG_ADCMD_VREFNXN | JZ_ADC_REG_ADCMD_VREFPVDD33
                       | JZ_ADC_REG_ADCMD_YPADC,
                       adc->base + JZ_ADC_REG_ADCMD);

                /* First channel (INGENIC_ADC_TOUCH_XP): sample XP vs. GND */
                writel(JZ_ADC_REG_ADCMD_YNGRU
                       | JZ_ADC_REG_ADCMD_VREFNYN | JZ_ADC_REG_ADCMD_VREFPVDD33
                       | JZ_ADC_REG_ADCMD_XPADC,
                       adc->base + JZ_ADC_REG_ADCMD);
        }

        if (mask & 0xc) {
                /* Fourth channel (INGENIC_ADC_TOUCH_YN): sample YN vs. GND */
                writel(JZ_ADC_REG_ADCMD_XNGRU
                       | JZ_ADC_REG_ADCMD_VREFNXN | JZ_ADC_REG_ADCMD_VREFPVDD33
                       | JZ_ADC_REG_ADCMD_YNADC,
                       adc->base + JZ_ADC_REG_ADCMD);

                /* Third channel (INGENIC_ADC_TOUCH_XN): sample XN vs. GND */
                writel(JZ_ADC_REG_ADCMD_YNGRU
                       | JZ_ADC_REG_ADCMD_VREFNYN | JZ_ADC_REG_ADCMD_VREFPVDD33
                       | JZ_ADC_REG_ADCMD_XNADC,
                       adc->base + JZ_ADC_REG_ADCMD);
        }

        if (mask & 0x30) {
                /* Sixth channel (INGENIC_ADC_TOUCH_YD): sample YP vs. YN */
                writel(JZ_ADC_REG_ADCMD_VREFNYN | JZ_ADC_REG_ADCMD_VREFPVDD33
                       | JZ_ADC_REG_ADCMD_YPADC,
                       adc->base + JZ_ADC_REG_ADCMD);

                /* Fifth channel (INGENIC_ADC_TOUCH_XD): sample XP vs. XN */
                writel(JZ_ADC_REG_ADCMD_VREFNXN | JZ_ADC_REG_ADCMD_VREFPVDD33
                       | JZ_ADC_REG_ADCMD_XPADC,
                       adc->base + JZ_ADC_REG_ADCMD);
        }

        /* We're done */
        writel(0, adc->base + JZ_ADC_REG_ADCMD);

        mutex_unlock(&adc->lock);
}

static void ingenic_adc_set_config(struct ingenic_adc *adc,
                                   uint32_t mask,
                                   uint32_t val)
{
        uint32_t cfg;

        mutex_lock(&adc->lock);

        cfg = readl(adc->base + JZ_ADC_REG_CFG) & ~mask;
        cfg |= val;
        writel(cfg, adc->base + JZ_ADC_REG_CFG);

        mutex_unlock(&adc->lock);
}

static void ingenic_adc_enable_unlocked(struct ingenic_adc *adc,
                                        int engine,
                                        bool enabled)
{
        u8 val;

        val = readb(adc->base + JZ_ADC_REG_ENABLE);

        if (enabled)
                val |= BIT(engine);
        else
                val &= ~BIT(engine);

        writeb(val, adc->base + JZ_ADC_REG_ENABLE);
}

static void ingenic_adc_enable(struct ingenic_adc *adc,
                               int engine,
                               bool enabled)
{
        mutex_lock(&adc->lock);
        ingenic_adc_enable_unlocked(adc, engine, enabled);
        mutex_unlock(&adc->lock);
}

static int ingenic_adc_capture(struct ingenic_adc *adc,
                               int engine)
{
        u32 cfg;
        u8 val;
        int ret;

        /*
         * Disable CMD_SEL temporarily, because it causes wrong VBAT readings,
         * probably due to the switch of VREF. We must keep the lock here to
         * avoid races with the buffer enable/disable functions.
         */
        mutex_lock(&adc->lock);
        cfg = readl(adc->base + JZ_ADC_REG_CFG);
        writel(cfg & ~JZ_ADC_REG_CFG_CMD_SEL, adc->base + JZ_ADC_REG_CFG);

        ingenic_adc_enable_unlocked(adc, engine, true);
        ret = readb_poll_timeout(adc->base + JZ_ADC_REG_ENABLE, val,
                                 !(val & BIT(engine)), 250, 1000);
        if (ret)
                ingenic_adc_enable_unlocked(adc, engine, false);

        writel(cfg, adc->base + JZ_ADC_REG_CFG);
        mutex_unlock(&adc->lock);

        return ret;
}

static int ingenic_adc_write_raw(struct iio_dev *iio_dev,
                                 struct iio_chan_spec const *chan,
                                 int val,
                                 int val2,
                                 long m)
{
        struct ingenic_adc *adc = iio_priv(iio_dev);
        struct device *dev = iio_dev->dev.parent;
        int ret;

        switch (m) {
        case IIO_CHAN_INFO_SCALE:
                switch (chan->channel) {
                case INGENIC_ADC_BATTERY:
                        if (!adc->soc_data->battery_vref_mode)
                                return -EINVAL;

                        ret = clk_enable(adc->clk);
                        if (ret) {
                                dev_err(dev, "Failed to enable clock: %d\n",
                                        ret);
                                return ret;
                        }

                        if (val > JZ_ADC_BATTERY_LOW_VREF) {
                                ingenic_adc_set_config(adc,
                                                       JZ_ADC_REG_CFG_BAT_MD,
                                                       0);
                                adc->low_vref_mode = false;
                        } else {
                                ingenic_adc_set_config(adc,
                                                       JZ_ADC_REG_CFG_BAT_MD,
                                                       JZ_ADC_REG_CFG_BAT_MD);
                                adc->low_vref_mode = true;
                        }

                        clk_disable(adc->clk);

                        return 0;
                default:
                        return -EINVAL;
                }
        default:
                return -EINVAL;
        }
}

static const int jz4725b_adc_battery_raw_avail[] = {
        0, 1, (1 << JZ_ADC_BATTERY_LOW_VREF_BITS) - 1,
};

static const int jz4725b_adc_battery_scale_avail[] = {
        JZ4725B_ADC_BATTERY_HIGH_VREF, JZ4725B_ADC_BATTERY_HIGH_VREF_BITS,
        JZ_ADC_BATTERY_LOW_VREF, JZ_ADC_BATTERY_LOW_VREF_BITS,
};

static const int jz4740_adc_battery_raw_avail[] = {
        0, 1, (1 << JZ_ADC_BATTERY_LOW_VREF_BITS) - 1,
};

static const int jz4740_adc_battery_scale_avail[] = {
        JZ4740_ADC_BATTERY_HIGH_VREF, JZ4740_ADC_BATTERY_HIGH_VREF_BITS,
        JZ_ADC_BATTERY_LOW_VREF, JZ_ADC_BATTERY_LOW_VREF_BITS,
};

static const int jz4760_adc_battery_scale_avail[] = {
        JZ4760_ADC_BATTERY_VREF, JZ4770_ADC_BATTERY_VREF_BITS,
};

static const int jz4770_adc_battery_raw_avail[] = {
        0, 1, (1 << JZ4770_ADC_BATTERY_VREF_BITS) - 1,
};

static const int jz4770_adc_battery_scale_avail[] = {
        JZ4770_ADC_BATTERY_VREF, JZ4770_ADC_BATTERY_VREF_BITS,
};

static int jz4725b_adc_init_clk_div(struct device *dev, struct ingenic_adc *adc)
{
        struct clk *parent_clk;
        unsigned long parent_rate, rate;
        unsigned int div_main, div_10us;

        parent_clk = clk_get_parent(adc->clk);
        if (!parent_clk) {
                dev_err(dev, "ADC clock has no parent\n");
                return -ENODEV;
        }
        parent_rate = clk_get_rate(parent_clk);

        /*
         * The JZ4725B ADC works at 500 kHz to 8 MHz.
         * We pick the highest rate possible.
         * In practice we typically get 6 MHz, half of the 12 MHz EXT clock.
         */
        div_main = DIV_ROUND_UP(parent_rate, 8000000);
        div_main = clamp(div_main, 1u, 64u);
        rate = parent_rate / div_main;
        if (rate < 500000 || rate > 8000000) {
                dev_err(dev, "No valid divider for ADC main clock\n");
                return -EINVAL;
        }

        /* We also need a divider that produces a 10us clock. */
        div_10us = DIV_ROUND_UP(rate, 100000);

        writel(((div_10us - 1) << JZ4725B_ADC_REG_ADCLK_CLKDIV10US_LSB) |
               (div_main - 1) << JZ_ADC_REG_ADCLK_CLKDIV_LSB,
               adc->base + JZ_ADC_REG_ADCLK);

        return 0;
}

static int jz4770_adc_init_clk_div(struct device *dev, struct ingenic_adc *adc)
{
        struct clk *parent_clk;
        unsigned long parent_rate, rate;
        unsigned int div_main, div_ms, div_10us;

        parent_clk = clk_get_parent(adc->clk);
        if (!parent_clk) {
                dev_err(dev, "ADC clock has no parent\n");
                return -ENODEV;
        }
        parent_rate = clk_get_rate(parent_clk);

        /*
         * The JZ4770 ADC works at 20 kHz to 200 kHz.
         * We pick the highest rate possible.
         */
        div_main = DIV_ROUND_UP(parent_rate, 200000);
        div_main = clamp(div_main, 1u, 256u);
        rate = parent_rate / div_main;
        if (rate < 20000 || rate > 200000) {
                dev_err(dev, "No valid divider for ADC main clock\n");
                return -EINVAL;
        }

        /* We also need a divider that produces a 10us clock. */
        div_10us = DIV_ROUND_UP(rate, 10000);
        /* And another, which produces a 1ms clock. */
        div_ms = DIV_ROUND_UP(rate, 1000);

        writel(((div_ms - 1) << JZ4770_ADC_REG_ADCLK_CLKDIVMS_LSB) |
               ((div_10us - 1) << JZ4770_ADC_REG_ADCLK_CLKDIV10US_LSB) |
               (div_main - 1) << JZ_ADC_REG_ADCLK_CLKDIV_LSB,
               adc->base + JZ_ADC_REG_ADCLK);

        return 0;
}

static const struct iio_chan_spec jz4740_channels[] = {
        {
                .extend_name = "aux",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_AUX,
                .scan_index = -1,
        },
        {
                .extend_name = "battery",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW) |
                                                BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_BATTERY,
                .scan_index = -1,
        },
};

static const struct iio_chan_spec jz4760_channels[] = {
        {
                .extend_name = "aux",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_AUX0,
                .scan_index = -1,
        },
        {
                .extend_name = "aux1",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_AUX,
                .scan_index = -1,
        },
        {
                .extend_name = "aux2",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_AUX2,
                .scan_index = -1,
        },
        {
                .extend_name = "battery",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW) |
                                                BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_BATTERY,
                .scan_index = -1,
        },
};

static const struct iio_chan_spec jz4770_channels[] = {
        {
                .type = IIO_VOLTAGE,
                .indexed = 1,
                .channel = INGENIC_ADC_TOUCH_XP,
                .scan_index = 0,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 12,
                        .storagebits = 16,
                },
        },
        {
                .type = IIO_VOLTAGE,
                .indexed = 1,
                .channel = INGENIC_ADC_TOUCH_YP,
                .scan_index = 1,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 12,
                        .storagebits = 16,
                },
        },
        {
                .type = IIO_VOLTAGE,
                .indexed = 1,
                .channel = INGENIC_ADC_TOUCH_XN,
                .scan_index = 2,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 12,
                        .storagebits = 16,
                },
        },
        {
                .type = IIO_VOLTAGE,
                .indexed = 1,
                .channel = INGENIC_ADC_TOUCH_YN,
                .scan_index = 3,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 12,
                        .storagebits = 16,
                },
        },
        {
                .type = IIO_VOLTAGE,
                .indexed = 1,
                .channel = INGENIC_ADC_TOUCH_XD,
                .scan_index = 4,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 12,
                        .storagebits = 16,
                },
        },
        {
                .type = IIO_VOLTAGE,
                .indexed = 1,
                .channel = INGENIC_ADC_TOUCH_YD,
                .scan_index = 5,
                .scan_type = {
                        .sign = 'u',
                        .realbits = 12,
                        .storagebits = 16,
                },
        },
        {
                .extend_name = "aux",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_AUX,
                .scan_index = -1,
        },
        {
                .extend_name = "battery",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW) |
                                                BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_BATTERY,
                .scan_index = -1,
        },
        {
                .extend_name = "aux2",
                .type = IIO_VOLTAGE,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_SCALE),
                .indexed = 1,
                .channel = INGENIC_ADC_AUX2,
                .scan_index = -1,
        },
};

static const struct ingenic_adc_soc_data jz4725b_adc_soc_data = {
        .battery_high_vref = JZ4725B_ADC_BATTERY_HIGH_VREF,
        .battery_high_vref_bits = JZ4725B_ADC_BATTERY_HIGH_VREF_BITS,
        .battery_raw_avail = jz4725b_adc_battery_raw_avail,
        .battery_raw_avail_size = ARRAY_SIZE(jz4725b_adc_battery_raw_avail),
        .battery_scale_avail = jz4725b_adc_battery_scale_avail,
        .battery_scale_avail_size = ARRAY_SIZE(jz4725b_adc_battery_scale_avail),
        .battery_vref_mode = true,
        .has_aux_md = false,
        .channels = jz4740_channels,
        .num_channels = ARRAY_SIZE(jz4740_channels),
        .init_clk_div = jz4725b_adc_init_clk_div,
};

static const struct ingenic_adc_soc_data jz4740_adc_soc_data = {
        .battery_high_vref = JZ4740_ADC_BATTERY_HIGH_VREF,
        .battery_high_vref_bits = JZ4740_ADC_BATTERY_HIGH_VREF_BITS,
        .battery_raw_avail = jz4740_adc_battery_raw_avail,
        .battery_raw_avail_size = ARRAY_SIZE(jz4740_adc_battery_raw_avail),
        .battery_scale_avail = jz4740_adc_battery_scale_avail,
        .battery_scale_avail_size = ARRAY_SIZE(jz4740_adc_battery_scale_avail),
        .battery_vref_mode = true,
        .has_aux_md = false,
        .channels = jz4740_channels,
        .num_channels = ARRAY_SIZE(jz4740_channels),
        .init_clk_div = NULL, /* no ADCLK register on JZ4740 */
};

static const struct ingenic_adc_soc_data jz4760_adc_soc_data = {
        .battery_high_vref = JZ4760_ADC_BATTERY_VREF,
        .battery_high_vref_bits = JZ4770_ADC_BATTERY_VREF_BITS,
        .battery_raw_avail = jz4770_adc_battery_raw_avail,
        .battery_raw_avail_size = ARRAY_SIZE(jz4770_adc_battery_raw_avail),
        .battery_scale_avail = jz4760_adc_battery_scale_avail,
        .battery_scale_avail_size = ARRAY_SIZE(jz4760_adc_battery_scale_avail),
        .battery_vref_mode = false,
        .has_aux_md = true,
        .channels = jz4760_channels,
        .num_channels = ARRAY_SIZE(jz4760_channels),
        .init_clk_div = jz4770_adc_init_clk_div,
};

static const struct ingenic_adc_soc_data jz4770_adc_soc_data = {
        .battery_high_vref = JZ4770_ADC_BATTERY_VREF,
        .battery_high_vref_bits = JZ4770_ADC_BATTERY_VREF_BITS,
        .battery_raw_avail = jz4770_adc_battery_raw_avail,
        .battery_raw_avail_size = ARRAY_SIZE(jz4770_adc_battery_raw_avail),
        .battery_scale_avail = jz4770_adc_battery_scale_avail,
        .battery_scale_avail_size = ARRAY_SIZE(jz4770_adc_battery_scale_avail),
        .battery_vref_mode = false,
        .has_aux_md = true,
        .channels = jz4770_channels,
        .num_channels = ARRAY_SIZE(jz4770_channels),
        .init_clk_div = jz4770_adc_init_clk_div,
};

static int ingenic_adc_read_avail(struct iio_dev *iio_dev,
                                  struct iio_chan_spec const *chan,
                                  const int **vals,
                                  int *type,
                                  int *length,
                                  long m)
{
        struct ingenic_adc *adc = iio_priv(iio_dev);

        switch (m) {
        case IIO_CHAN_INFO_RAW:
                *type = IIO_VAL_INT;
                *length = adc->soc_data->battery_raw_avail_size;
                *vals = adc->soc_data->battery_raw_avail;
                return IIO_AVAIL_RANGE;
        case IIO_CHAN_INFO_SCALE:
                *type = IIO_VAL_FRACTIONAL_LOG2;
                *length = adc->soc_data->battery_scale_avail_size;
                *vals = adc->soc_data->battery_scale_avail;
                return IIO_AVAIL_LIST;
        default:
                return -EINVAL;
        }
}

static int ingenic_adc_read_chan_info_raw(struct iio_dev *iio_dev,
                                          struct iio_chan_spec const *chan,
                                          int *val)
{
        int cmd, ret, engine = (chan->channel == INGENIC_ADC_BATTERY);
        struct ingenic_adc *adc = iio_priv(iio_dev);

        ret = clk_enable(adc->clk);
        if (ret) {
                dev_err(iio_dev->dev.parent, "Failed to enable clock: %d\n",
                        ret);
                return ret;
        }

        /* We cannot sample the aux channels in parallel. */
        mutex_lock(&adc->aux_lock);
        if (adc->soc_data->has_aux_md && engine == 0) {
                switch (chan->channel) {
                case INGENIC_ADC_AUX0:
                        cmd = 0;
                        break;
                case INGENIC_ADC_AUX:
                        cmd = 1;
                        break;
                case INGENIC_ADC_AUX2:
                        cmd = 2;
                        break;
                }

                ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_AUX_MD, cmd);
        }

        ret = ingenic_adc_capture(adc, engine);
        if (ret)
                goto out;

        switch (chan->channel) {
        case INGENIC_ADC_AUX0:
        case INGENIC_ADC_AUX:
        case INGENIC_ADC_AUX2:
                *val = readw(adc->base + JZ_ADC_REG_ADSDAT);
                break;
        case INGENIC_ADC_BATTERY:
                *val = readw(adc->base + JZ_ADC_REG_ADBDAT);
                break;
        }

        ret = IIO_VAL_INT;
out:
        mutex_unlock(&adc->aux_lock);
        clk_disable(adc->clk);

        return ret;
}

static int ingenic_adc_read_raw(struct iio_dev *iio_dev,
                                struct iio_chan_spec const *chan,
                                int *val,
                                int *val2,
                                long m)
{
        struct ingenic_adc *adc = iio_priv(iio_dev);

        switch (m) {
        case IIO_CHAN_INFO_RAW:
                return ingenic_adc_read_chan_info_raw(iio_dev, chan, val);
        case IIO_CHAN_INFO_SCALE:
                switch (chan->channel) {
                case INGENIC_ADC_AUX0:
                case INGENIC_ADC_AUX:
                case INGENIC_ADC_AUX2:
                        *val = JZ_ADC_AUX_VREF;
                        *val2 = JZ_ADC_AUX_VREF_BITS;
                        break;
                case INGENIC_ADC_BATTERY:
                        if (adc->low_vref_mode) {
                                *val = JZ_ADC_BATTERY_LOW_VREF;
                                *val2 = JZ_ADC_BATTERY_LOW_VREF_BITS;
                        } else {
                                *val = adc->soc_data->battery_high_vref;
                                *val2 = adc->soc_data->battery_high_vref_bits;
                        }
                        break;
                }

                return IIO_VAL_FRACTIONAL_LOG2;
        default:
                return -EINVAL;
        }
}

static int ingenic_adc_fwnode_xlate(struct iio_dev *iio_dev,
                                    const struct fwnode_reference_args *iiospec)
{
        int i;

        if (!iiospec->nargs)
                return -EINVAL;

        for (i = 0; i < iio_dev->num_channels; ++i)
                if (iio_dev->channels[i].channel == iiospec->args[0])
                        return i;

        return -EINVAL;
}

static const struct iio_info ingenic_adc_info = {
        .write_raw = ingenic_adc_write_raw,
        .read_raw = ingenic_adc_read_raw,
        .read_avail = ingenic_adc_read_avail,
        .fwnode_xlate = ingenic_adc_fwnode_xlate,
};

static int ingenic_adc_buffer_enable(struct iio_dev *iio_dev)
{
        struct ingenic_adc *adc = iio_priv(iio_dev);
        int ret;

        ret = clk_enable(adc->clk);
        if (ret) {
                dev_err(iio_dev->dev.parent, "Failed to enable clock: %d\n",
                        ret);
                return ret;
        }

        /* It takes significant time for the touchscreen hw to stabilize. */
        msleep(50);
        ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_TOUCH_OPS_MASK,
                               JZ_ADC_REG_CFG_SAMPLE_NUM(4) |
                               JZ_ADC_REG_CFG_PULL_UP(4));

        writew(80, adc->base + JZ_ADC_REG_ADWAIT);
        writew(2, adc->base + JZ_ADC_REG_ADSAME);
        writeb((u8)~JZ_ADC_IRQ_TOUCH, adc->base + JZ_ADC_REG_CTRL);
        writel(0, adc->base + JZ_ADC_REG_ADTCH);

        ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_CMD_SEL,
                               JZ_ADC_REG_CFG_CMD_SEL);
        ingenic_adc_set_adcmd(iio_dev, iio_dev->active_scan_mask[0]);

        ingenic_adc_enable(adc, 2, true);

        return 0;
}

static int ingenic_adc_buffer_disable(struct iio_dev *iio_dev)
{
        struct ingenic_adc *adc = iio_priv(iio_dev);

        ingenic_adc_enable(adc, 2, false);

        ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_CMD_SEL, 0);

        writeb(0xff, adc->base + JZ_ADC_REG_CTRL);
        writeb(0xff, adc->base + JZ_ADC_REG_STATUS);
        ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_TOUCH_OPS_MASK, 0);
        writew(0, adc->base + JZ_ADC_REG_ADSAME);
        writew(0, adc->base + JZ_ADC_REG_ADWAIT);
        clk_disable(adc->clk);

        return 0;
}

static const struct iio_buffer_setup_ops ingenic_buffer_setup_ops = {
        .postenable = &ingenic_adc_buffer_enable,
        .predisable = &ingenic_adc_buffer_disable
};

static irqreturn_t ingenic_adc_irq(int irq, void *data)
{
        struct iio_dev *iio_dev = data;
        struct ingenic_adc *adc = iio_priv(iio_dev);
        unsigned long mask = iio_dev->active_scan_mask[0];
        unsigned int i;
        u32 tdat[3];

        for (i = 0; i < ARRAY_SIZE(tdat); mask >>= 2, i++) {
                if (mask & 0x3)
                        tdat[i] = readl(adc->base + JZ_ADC_REG_ADTCH);
                else
                        tdat[i] = 0;
        }

        iio_push_to_buffers(iio_dev, tdat);
        writeb(JZ_ADC_IRQ_TOUCH, adc->base + JZ_ADC_REG_STATUS);

        return IRQ_HANDLED;
}

static int ingenic_adc_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct iio_dev *iio_dev;
        struct ingenic_adc *adc;
        const struct ingenic_adc_soc_data *soc_data;
        int irq, ret;

        soc_data = device_get_match_data(dev);
        if (!soc_data)
                return -EINVAL;

        iio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
        if (!iio_dev)
                return -ENOMEM;

        adc = iio_priv(iio_dev);
        mutex_init(&adc->lock);
        mutex_init(&adc->aux_lock);
        adc->soc_data = soc_data;

        irq = platform_get_irq(pdev, 0);
        if (irq < 0)
                return irq;

        ret = devm_request_irq(dev, irq, ingenic_adc_irq, 0,
                               dev_name(dev), iio_dev);
        if (ret < 0) {
                dev_err(dev, "Failed to request irq: %d\n", ret);
                return ret;
        }

        adc->base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(adc->base))
                return PTR_ERR(adc->base);

        adc->clk = devm_clk_get_prepared(dev, "adc");
        if (IS_ERR(adc->clk)) {
                dev_err(dev, "Unable to get clock\n");
                return PTR_ERR(adc->clk);
        }

        ret = clk_enable(adc->clk);
        if (ret) {
                dev_err(dev, "Failed to enable clock\n");
                return ret;
        }

        /* Set clock dividers. */
        if (soc_data->init_clk_div) {
                ret = soc_data->init_clk_div(dev, adc);
                if (ret) {
                        clk_disable_unprepare(adc->clk);
                        return ret;
                }
        }

        /* Put hardware in a known passive state. */
        writeb(0x00, adc->base + JZ_ADC_REG_ENABLE);
        writeb(0xff, adc->base + JZ_ADC_REG_CTRL);

        /* JZ4760B specific */
        if (device_property_present(dev, "ingenic,use-internal-divider"))
                ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_VBAT_SEL,
                                            JZ_ADC_REG_CFG_VBAT_SEL);
        else
                ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_VBAT_SEL, 0);

        usleep_range(2000, 3000); /* Must wait at least 2ms. */
        clk_disable(adc->clk);

        iio_dev->name = "jz-adc";
        iio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
        iio_dev->setup_ops = &ingenic_buffer_setup_ops;
        iio_dev->channels = soc_data->channels;
        iio_dev->num_channels = soc_data->num_channels;
        iio_dev->info = &ingenic_adc_info;

        ret = devm_iio_device_register(dev, iio_dev);
        if (ret)
                dev_err(dev, "Unable to register IIO device\n");

        return ret;
}

static const struct of_device_id ingenic_adc_of_match[] = {
        { .compatible = "ingenic,jz4725b-adc", .data = &jz4725b_adc_soc_data, },
        { .compatible = "ingenic,jz4740-adc", .data = &jz4740_adc_soc_data, },
        { .compatible = "ingenic,jz4760-adc", .data = &jz4760_adc_soc_data, },
        { .compatible = "ingenic,jz4760b-adc", .data = &jz4760_adc_soc_data, },
        { .compatible = "ingenic,jz4770-adc", .data = &jz4770_adc_soc_data, },
        { }
};
MODULE_DEVICE_TABLE(of, ingenic_adc_of_match);

static struct platform_driver ingenic_adc_driver = {
        .driver = {
                .name = "ingenic-adc",
                .of_match_table = ingenic_adc_of_match,
        },
        .probe = ingenic_adc_probe,
};
module_platform_driver(ingenic_adc_driver);
MODULE_DESCRIPTION("ADC driver for the Ingenic JZ47xx SoCs");
MODULE_LICENSE("GPL v2");