iio: saradc: add support for g12a
authorXingyu Chen <xingyu.chen@amlogic.com>
Thu, 18 Jan 2018 08:25:35 +0000 (16:25 +0800)
committerYixun Lan <yixun.lan@amlogic.com>
Fri, 2 Mar 2018 07:07:52 +0000 (15:07 +0800)
PD#156734: iio: saradc: add support for g12a

Add period sampling mode for g12a and next, here are a brief overview:

1. Enable period sampling mode

echo 1  > /sys/bus/iio/devices/iio:device0/buffer/enable

2. Set the sampling interval
The sampling interval can be set in dts by the two params:
<1> amlogic,delay-per-tick
<2> amlogic,ticks-per-period

If there are no define the params above, and the channel list is
sampled every 1ms by default.

3. Read sampling data from buffer
this currently provides no buffer events so it is up to userspace to
work out how often to read from the buffer. therefore, if you want to
read continuous sampling data from buffer, please use application.

Eg:
[kernel]/tools/iio/iio_generic_buffer.c

Change-Id: I27f904e2736768eacc9d73ff24078bd659e37049
Signed-off-by: Xingyu Chen <xingyu.chen@amlogic.com>
Documentation/devicetree/bindings/iio/adc/amlogic,meson-saradc.txt
arch/arm/configs/meson32_defconfig
arch/arm64/boot/dts/amlogic/mesong12a.dtsi
arch/arm64/configs/meson64_defconfig
arch/arm64/configs/meson64_smarthome_defconfig
drivers/amlogic/iio/adc/Kconfig
drivers/amlogic/iio/adc/meson_saradc.c

index 0474770..f41e945 100644 (file)
@@ -2,6 +2,7 @@
 
 Required properties:
 - compatible:  depending on the SoC this should be one of:
+                       - "amlogic,meson-g12a-saradc" for G12A
                        - "amlogic,meson-txlx-saradc" for TXLX
                        - "amlogic,meson-axg-saradc" for AXG
                        - "amlogic,meson-gxl-saradc" for GXL
index 8d688b8..cd9c3a0 100644 (file)
@@ -320,6 +320,8 @@ CONFIG_PM_DEVFREQ=y
 CONFIG_EXTCON=y
 CONFIG_MEMORY=y
 CONFIG_IIO=y
+CONFIG_IIO_BUFFER=y
+CONFIG_IIO_KFIFO_BUF=y
 CONFIG_PWM=y
 CONFIG_RESET_CONTROLLER=y
 CONFIG_GENERIC_PHY=y
index 34e2807..dc5f0d3 100644 (file)
                clocks = <&xtal>;
        };
 
+       saradc:saradc {
+               compatible = "amlogic,meson-g12a-saradc";
+               status = "okay";
+               #io-channel-cells = <1>;
+               clocks = <&xtal>, <&clkc CLKID_SARADC_GATE>;
+               clock-names = "xtal", "saradc_clk";
+               interrupts = <GIC_SPI 200 IRQ_TYPE_EDGE_RISING>;
+               reg = <0x0 0xff809000 0x0 0x48>;
+       };
+
        soc {
                compatible = "simple-bus";
                #address-cells = <2>;
index 97a78e3..6af564a 100644 (file)
@@ -507,6 +507,8 @@ CONFIG_DEVFREQ_GOV_POWERSAVE=y
 CONFIG_DEVFREQ_GOV_USERSPACE=y
 CONFIG_EXTCON=y
 CONFIG_IIO=y
+CONFIG_IIO_BUFFER=y
+CONFIG_IIO_KFIFO_BUF=y
 CONFIG_PWM=y
 CONFIG_ANDROID=y
 CONFIG_ANDROID_BINDER_IPC=y
index 9a2af3d..8587ebe 100644 (file)
@@ -464,6 +464,8 @@ CONFIG_DEVFREQ_GOV_POWERSAVE=y
 CONFIG_DEVFREQ_GOV_USERSPACE=y
 CONFIG_EXTCON=y
 CONFIG_IIO=y
+CONFIG_IIO_BUFFER=y
+CONFIG_IIO_KFIFO_BUF=y
 CONFIG_PWM=y
 CONFIG_ANDROID=y
 CONFIG_ANDROID_BINDER_IPC=y
index 462f825..351cee6 100644 (file)
@@ -9,6 +9,8 @@ config AMLOGIC_SARADC
        bool "Meson SAR ADC support"
        depends on REGMAP_MMIO
        depends on IIO
+       depends on IIO_BUFFER
+       depends on IIO_KFIFO_BUF
        default n
        help
          Say Y here if you want to use the Meson SAR ADC.
index 09f7324..aaa859e 100644 (file)
@@ -13,8 +13,8 @@
  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  * more details.
  *
- * The sar adc is work in polling mode by default, or work in IRQ mode
- * by defining the macro MESON_SAR_ADC_IRQ_MODE.
+ * The sar adc is work in polling mode for single sampling, or work in IRQ mode
+ * for periodic sampling.
  */
 
 #include <linux/bitfield.h>
@@ -35,6 +35,9 @@
 #include <linux/amlogic/iomap.h>
 #include <dt-bindings/iio/adc/amlogic-saradc.h>
 #include <asm/barrier.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/slab.h>
 
 #define MESON_SAR_ADC_REG0                                     0x00
        #define MESON_SAR_ADC_REG0_PANEL_DETECT                 BIT(31)
@@ -60,6 +63,7 @@
 
 #define MESON_SAR_ADC_CHAN_LIST                                        0x04
        #define MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK          GENMASK(26, 24)
+       #define MESON_SAR_ADC_CHAN_LIST_ENTRY_SHIFT(_chan)      (_chan * 3)
        #define MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(_chan)       \
                                        (GENMASK(2, 0) << ((_chan) * 3))
 
 #define MESON_SAR_ADC_REG11                                    0x2c
        #define MESON_SAR_ADC_REG11_VREF_SEL                    BIT(0)
        #define MESON_SAR_ADC_REG11_BANDGAP_EN                  BIT(13)
+       #define MESON_SAR_ADC_REG11_CHNL_REGS_EN                BIT(30)
+       #define MESON_SAR_ADC_REG11_FIFO_EN                     BIT(31)
 
 #define MESON_SAR_ADC_REG13                                    0x34
        #define MESON_SAR_ADC_REG13_12BIT_CALIBRATION_MASK      GENMASK(13, 8)
 
-#define MESON_SAR_ADC_MAX_FIFO_SIZE                            16
+/* NOTE: the registers below is introduced first on G12A platform */
+#define MESON_SAR_ADC_CHNLX_BASE                               0x38
+#define MESON_SAR_ADC_CHNLX_SAMPLE_VALUE_SHIFT(_chan)          \
+                                       ((_chan) * 16)
+#define MESON_SAR_ADC_CHNLX_ID_SHIFT(_chan)                    \
+                                       (12 + (_chan) * 16)
+#define MESON_SAR_ADC_CHNLX_VALID_SHIFT(_chan)                 \
+                                       (15 + (_chan) * 16)
+#define MESON_SAR_ADC_CHNL01                                   0x38
+#define MESON_SAR_ADC_CHNL23                                   0x3c
+#define MESON_SAR_ADC_CHNL45                                   0x40
+#define MESON_SAR_ADC_CHNL67                                   0x44
+
+#define MESON_SAR_ADC_MAX_FIFO_SIZE                            32
 #define MESON_SAR_ADC_TIMEOUT                                  100 /* ms */
 
 #define P_HHI_DPLL_TOP_0       0x10c6
        .type = IIO_VOLTAGE,                                            \
        .indexed = 1,                                                   \
        .channel = _chan,                                               \
-       .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
-                               BIT(IIO_CHAN_INFO_AVERAGE_RAW) |         \
+       .scan_index = _chan,                                            \
+       .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |                  \
+                               BIT(IIO_CHAN_INFO_AVERAGE_RAW) |        \
                                BIT(IIO_CHAN_INFO_PROCESSED),           \
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |          \
                                BIT(IIO_CHAN_INFO_CALIBBIAS) |          \
                                BIT(IIO_CHAN_INFO_CALIBSCALE),          \
+       .scan_type = {                                                  \
+               .sign = 'u',                                            \
+               .storagebits = 16,                                      \
+               .shift = 0,                                             \
+               .endianness = IIO_CPU,                                  \
+       },                                                              \
        .datasheet_name = "SAR_ADC_CH"#_chan,                           \
 }
 
@@ -231,6 +257,12 @@ static const struct iio_chan_spec meson_sar_adc_iio_channels[] = {
        IIO_CHAN_SOFT_TIMESTAMP(8),
 };
 
+enum meson_sar_adc_sample_mode {
+       SINGLE_MODE,
+       PERIOD_MODE,
+       MAX_MODE,
+};
+
 enum meson_sar_adc_avg_mode {
        NO_AVERAGING = 0x0,
        MEAN_AVERAGING = 0x1,
@@ -284,6 +316,8 @@ struct meson_sar_adc_reg_diff {
  * @obt_temp_chan6: whether to read data of temp sensor by channel 6
  * @has_bl30_integration:
  * @vref_sel: txlx and later: VDDA; others(txl etc): calibration voltage
+ * @period_support: periodic sampling support
+ * @has_chnl_regs: whether support for chnl[X] registers
  * @resolution: gxl and later: 12bit; others(gxtvbb etc): 10bit
  * @name:
  * @regs_diff: to describe the differences of the registers
@@ -292,31 +326,36 @@ struct meson_sar_adc_data {
        bool                    obt_temp_chan6;
        bool                    has_bl30_integration;
        bool                    vref_sel;
-       unsigned int    resolution;
+       bool                    period_support;
+       bool                    has_chnl_regs;
+       unsigned int            resolution;
        const char              *name;
        struct meson_sar_adc_reg_diff regs_diff;
 };
 
 struct meson_sar_adc_priv {
-       struct regmap           *regmap;
-       const struct meson_sar_adc_data         *data;
+       struct regmap                   *regmap;
+       const struct meson_sar_adc_data *data;
        struct clk                      *clkin;
        struct clk                      *clk81_gate;
        struct clk                      *adc_clk;
-       struct clk_gate         clk_gate;
+       struct clk_gate                 clk_gate;
        struct clk                      *adc_div_clk;
-       struct clk_divider      clk_div;
-       struct completion       done;
-       int                                     calibbias;
-       int                                     calibscale;
-       int                                     chan7_mux_sel;
+       struct clk_divider              clk_div;
+       int                             calibbias;
+       int                             calibscale;
+       int                             chan7_mux_sel;
+       int                             delay_per_tick;
+       int                             ticks_per_period;
+       int                             active_channel_cnt;
+       u8                              *datum_buf;
 };
 
 static const struct regmap_config meson_sar_adc_regmap_config = {
        .reg_bits = 8,
        .val_bits = 32,
        .reg_stride = 4,
-       .max_register = MESON_SAR_ADC_REG13,
+       .max_register = MESON_SAR_ADC_CHNL67,
 };
 
 static unsigned int meson_sar_adc_get_fifo_count(struct iio_dev *indio_dev)
@@ -340,7 +379,6 @@ static int meson_sar_adc_calib_val(struct iio_dev *indio_dev, int val)
        return clamp(tmp, 0, (1 << priv->data->resolution) - 1);
 }
 
-#ifndef MESON_SAR_ADC_IRQ_MODE
 static int meson_sar_adc_wait_busy_clear(struct iio_dev *indio_dev)
 {
        struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
@@ -361,7 +399,6 @@ static int meson_sar_adc_wait_busy_clear(struct iio_dev *indio_dev)
 
        return 0;
 }
-#endif
 
 static int meson_sar_adc_read_raw_sample(struct iio_dev *indio_dev,
                                         const struct iio_chan_spec *chan,
@@ -370,14 +407,8 @@ static int meson_sar_adc_read_raw_sample(struct iio_dev *indio_dev,
        struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
        int regval, fifo_chan, fifo_val, count;
 
-#ifdef MESON_SAR_ADC_IRQ_MODE
-       if (!wait_for_completion_timeout(&priv->done,
-                               msecs_to_jiffies(MESON_SAR_ADC_TIMEOUT)))
-               return -ETIMEDOUT;
-#else
        if (meson_sar_adc_wait_busy_clear(indio_dev))
                return -ETIMEDOUT;
-#endif
 
        count = meson_sar_adc_get_fifo_count(indio_dev);
        if (count != 1) {
@@ -404,6 +435,46 @@ static int meson_sar_adc_read_raw_sample(struct iio_dev *indio_dev,
        return 0;
 }
 
+static int meson_sar_adc_read_raw_sample_from_chnl(struct iio_dev *indio_dev,
+                                       const struct iio_chan_spec *chan,
+                                       int *val)
+{
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+       unsigned int regval;
+       int grp_off;
+       int chan_off;
+       int fifo_chan;
+       int fifo_val;
+       bool is_valid;
+
+       grp_off = (chan->channel / 2) << 2;
+       chan_off = chan->channel % 2;
+
+       regmap_read(priv->regmap,
+               MESON_SAR_ADC_CHNLX_BASE + grp_off, &regval);
+
+       is_valid = (regval >> MESON_SAR_ADC_CHNLX_VALID_SHIFT(chan_off)) & 0x1;
+       if (!is_valid) {
+               dev_err(&indio_dev->dev,
+                       "ADC chnl reg have no valid sampling data\n");
+               return -EINVAL;
+       }
+
+       fifo_chan = (regval >> MESON_SAR_ADC_CHNLX_ID_SHIFT(chan_off)) & 0x7;
+       if (fifo_chan != chan->channel) {
+               dev_err(&indio_dev->dev,
+                       "ADC Dout entry belongs to channel %d instead of %d\n",
+                       fifo_chan, chan->channel);
+               return -EINVAL;
+       }
+       fifo_val = regval >> MESON_SAR_ADC_CHNLX_SAMPLE_VALUE_SHIFT(chan_off);
+       fifo_val &= GENMASK(priv->data->resolution - 1, 0);
+
+       /* to fix the sample value by software */
+       *val = meson_sar_adc_calib_val(indio_dev, fifo_val);
+
+       return 0;
+}
 static void meson_sar_adc_set_averaging(struct iio_dev *indio_dev,
                                        const struct iio_chan_spec *chan,
                                        enum meson_sar_adc_avg_mode mode,
@@ -441,7 +512,8 @@ static int meson_sar_adc_temp_sensor_init(struct iio_dev *indio_dev)
 }
 
 static void meson_sar_adc_enable_channel(struct iio_dev *indio_dev,
-                                       const struct iio_chan_spec *chan)
+                                       const struct iio_chan_spec *chan,
+                                       unsigned char idx)
 {
        struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
        u32 regval;
@@ -451,27 +523,15 @@ static void meson_sar_adc_enable_channel(struct iio_dev *indio_dev,
         * time. to keep it simple we're only working with one *internal*
         * channel, which starts counting at index 0 (which means: count = 1).
         */
-       regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0);
+
+       regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, idx);
        regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_LIST,
                           MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval);
 
        /* map channel index 0 to the channel which we want to read */
-       regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(0),
-                           chan->channel);
+       regval = chan->channel << MESON_SAR_ADC_CHAN_LIST_ENTRY_SHIFT(idx),
        regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_LIST,
-                          MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(0), regval);
-
-       regval = FIELD_PREP(MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK,
-                           chan->channel);
-       regmap_update_bits(priv->regmap, MESON_SAR_ADC_DETECT_IDLE_SW,
-                          MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK,
-                          regval);
-
-       regval = FIELD_PREP(MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK,
-                           chan->channel);
-       regmap_update_bits(priv->regmap, MESON_SAR_ADC_DETECT_IDLE_SW,
-                          MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK,
-                          regval);
+                          MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(idx), regval);
 
        if (IS_CHAN6(chan->channel)) {
                if (priv->data->obt_temp_chan6)
@@ -500,15 +560,6 @@ static void meson_sar_adc_start_sample_engine(struct iio_dev *indio_dev)
 {
        struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
 
-#ifdef MESON_SAR_ADC_IRQ_MODE
-       reinit_completion(&priv->done);
-#endif
-
-#ifdef MESON_SAR_ADC_IRQ_MODE
-       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
-                          MESON_SAR_ADC_REG0_FIFO_IRQ_EN,
-                          MESON_SAR_ADC_REG0_FIFO_IRQ_EN);
-#endif
        regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
                           MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE,
                           MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE);
@@ -522,10 +573,6 @@ static void meson_sar_adc_stop_sample_engine(struct iio_dev *indio_dev)
 {
        struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
 
-#ifdef MESON_SAR_ADC_IRQ_MODE
-       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
-                          MESON_SAR_ADC_REG0_FIFO_IRQ_EN, 0);
-#endif
        regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
                           MESON_SAR_ADC_REG0_SAMPLING_STOP,
                           MESON_SAR_ADC_REG0_SAMPLING_STOP);
@@ -599,19 +646,33 @@ static int meson_sar_adc_get_sample(struct iio_dev *indio_dev,
                                    enum meson_sar_adc_num_samples avg_samples,
                                    int *val)
 {
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
        int ret;
+
        ret = meson_sar_adc_lock(indio_dev);
        if (ret) {
                meson_sar_adc_unlock(indio_dev);
                return ret;
        }
 
+       if (iio_buffer_enabled(indio_dev)) {
+               if (priv->data->has_chnl_regs) {
+                       ret = meson_sar_adc_read_raw_sample_from_chnl(indio_dev,
+                               chan, val);
+                       meson_sar_adc_unlock(indio_dev);
+
+                       return  (ret == 0) ? IIO_VAL_INT : ret;
+               }
+               meson_sar_adc_unlock(indio_dev);
+               return -EBUSY;
+       }
+
        /* clear the FIFO to make sure we're not reading old values */
        meson_sar_adc_clear_fifo(indio_dev);
 
        meson_sar_adc_set_averaging(indio_dev, chan, avg_mode, avg_samples);
 
-       meson_sar_adc_enable_channel(indio_dev, chan);
+       meson_sar_adc_enable_channel(indio_dev, chan, 0);
 
        meson_sar_adc_start_sample_engine(indio_dev);
        ret = meson_sar_adc_read_raw_sample(indio_dev, chan, val);
@@ -711,6 +772,19 @@ static int meson_sar_adc_iio_info_write_raw(struct iio_dev *indio_dev,
        }
 }
 
+static int meson_sar_adc_update_scan_mode(struct iio_dev *indio_dev,
+                                   const unsigned long *scan_mask)
+{
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+
+       kfree(priv->datum_buf);
+       priv->datum_buf = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
+       if (!priv->datum_buf)
+               return -ENOMEM;
+
+       return 0;
+}
+
 static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
 
                void __iomem *base)
@@ -845,11 +919,7 @@ static int meson_sar_adc_hw_enable(struct iio_dev *indio_dev)
                meson_sar_adc_unlock(indio_dev);
                return ret;
        }
-#ifdef MESON_SAR_ADC_IRQ_MODE
-       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
-                          MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK,
-                          FIELD_PREP(MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, 1));
-#endif
+
        regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11,
                           MESON_SAR_ADC_REG11_BANDGAP_EN,
                           MESON_SAR_ADC_REG11_BANDGAP_EN);
@@ -911,7 +981,7 @@ static int meson_sar_adc_hw_disable(struct iio_dev *indio_dev)
 
        return 0;
 }
-#ifdef MESON_SAR_ADC_IRQ_MODE
+
 static irqreturn_t meson_sar_adc_irq(int irq, void *data)
 {
        struct iio_dev *indio_dev = data;
@@ -926,11 +996,46 @@ static irqreturn_t meson_sar_adc_irq(int irq, void *data)
        if (cnt < threshold)
                return IRQ_NONE;
 
-       complete(&priv->done);
+       disable_irq_nosync(irq);
+
+       return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t meson_sar_adc_worker(int irq, void *data)
+{
+       struct iio_dev *indio_dev = data;
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+       u16 fifo_cnt;
+       u16 fifo_val;
+       u32 regval;
+       u32 i = 0;
+       u32 j = 0;
+
+       fifo_cnt = meson_sar_adc_get_fifo_count(indio_dev);
+
+       for (j = 0; j < fifo_cnt; j = j + i) {
+               for (i = 0; i < priv->active_channel_cnt; i++) {
+                       regmap_read(priv->regmap,
+                               MESON_SAR_ADC_FIFO_RD, &regval);
+
+                       fifo_val = FIELD_GET(
+                               MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK,
+                               regval);
+                       fifo_val &= GENMASK(priv->data->resolution - 1, 0);
+
+                       priv->datum_buf[i*2] = fifo_val & 0xff;
+                       priv->datum_buf[i*2+1] = (fifo_val >> 8) & 0xff;
+               }
+               iio_push_to_buffers_with_timestamp(indio_dev, priv->datum_buf,
+                       iio_get_time_ns(indio_dev));
+       }
+
+       meson_sar_adc_clear_fifo(indio_dev);
+       enable_irq(irq);
 
        return IRQ_HANDLED;
 }
-#endif
+
 static int meson_sar_adc_calib(struct iio_dev *indio_dev)
 {
        struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
@@ -972,6 +1077,167 @@ out:
        return ret;
 }
 
+static int meson_sar_adc_sample_mode_set(struct iio_dev *indio_dev,
+               enum meson_sar_adc_sample_mode mode)
+{
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+
+       if (mode != SINGLE_MODE && mode != PERIOD_MODE)
+               return -EINVAL;
+
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
+                       MESON_SAR_ADC_REG0_SAMPLING_STOP,
+                       (mode == SINGLE_MODE) ?
+                       MESON_SAR_ADC_REG0_SAMPLING_STOP : 0);
+
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
+                       MESON_SAR_ADC_REG0_CONTINUOUS_EN,
+                       (mode == PERIOD_MODE) ?
+                       MESON_SAR_ADC_REG0_CONTINUOUS_EN : 0);
+
+       return 0;
+}
+
+static void meson_sar_adc_chan_spec_update(struct iio_dev *indio_dev)
+{
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+       struct iio_chan_spec *chan;
+       int i;
+
+       for (i = 0; i < indio_dev->num_channels; i++) {
+               chan = (struct iio_chan_spec *)indio_dev->channels + i;
+               if (chan->channel < 0)
+                       continue;
+               chan->scan_type.realbits = priv->data->resolution;
+       }
+}
+
+static int meson_sar_adc_iio_buffer_setup(struct iio_dev *indio_dev,
+               irqreturn_t (*pollfunc_bh)(int irq, void *p),
+               irqreturn_t (*pollfunc_th)(int irq, void *p),
+               int irq, unsigned long flags,
+               const struct iio_buffer_setup_ops *setup_ops) {
+
+       struct iio_buffer *buffer;
+       int ret;
+
+       buffer = iio_kfifo_allocate();
+       if (!buffer)
+               return -ENOMEM;
+
+       iio_device_attach_buffer(indio_dev, buffer);
+
+       ret = devm_request_threaded_irq(indio_dev->dev.parent, irq,
+                       pollfunc_th,
+                       pollfunc_bh,
+                       flags,
+                       indio_dev->name,
+                       indio_dev);
+       if (ret)
+               goto error_kfifo_free;
+
+       indio_dev->setup_ops = setup_ops;
+       indio_dev->modes |= INDIO_BUFFER_SOFTWARE;
+
+       return 0;
+
+error_kfifo_free:
+       iio_kfifo_free(indio_dev->buffer);
+
+       return ret;
+}
+
+static int meson_sar_adc_iio_buffer_cleanup(struct iio_dev *indio_dev)
+{
+       iio_kfifo_free(indio_dev->buffer);
+
+       return 0;
+}
+
+static int meson_sar_adc_buffer_postenable(struct iio_dev *indio_dev)
+{
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+       const struct iio_chan_spec *chan;
+       unsigned char idx = 0;
+       unsigned char bit;
+
+       meson_sar_adc_sample_mode_set(indio_dev, PERIOD_MODE);
+
+       /* set sampling period time */
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3,
+                          MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK,
+                          FIELD_PREP(MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK,
+                                     priv->delay_per_tick));
+
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3,
+                          MESON_SAR_ADC_REG3_BLOCK_DLY_MASK,
+                          FIELD_PREP(MESON_SAR_ADC_REG3_BLOCK_DLY_MASK,
+                                     priv->ticks_per_period));
+
+       meson_sar_adc_clear_fifo(indio_dev);
+
+       for_each_set_bit(bit, indio_dev->active_scan_mask,
+                                       indio_dev->num_channels) {
+               chan = indio_dev->channels + bit;
+
+               if (chan->channel < 0)
+                       continue;
+               meson_sar_adc_enable_channel(indio_dev, chan, idx);
+               idx++;
+       }
+
+       priv->active_channel_cnt = idx;
+
+       /*
+        * generate interrupt when fifo contains N samples, and the N
+        * is required to align base on the number of active scan channel
+        */
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
+                       MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK,
+                       FIELD_PREP(MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK,
+                       16 - (16 % idx)));
+
+       /* enable irq */
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
+                       MESON_SAR_ADC_REG0_FIFO_IRQ_EN,
+                       MESON_SAR_ADC_REG0_FIFO_IRQ_EN);
+
+       /*
+        * enable chnl regs which save the sampling value for
+        * individual channel
+        */
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11,
+                       MESON_SAR_ADC_REG11_CHNL_REGS_EN,
+                       MESON_SAR_ADC_REG11_CHNL_REGS_EN);
+
+       meson_sar_adc_start_sample_engine(indio_dev);
+
+       return 0;
+}
+
+static int meson_sar_adc_buffer_predisable(struct iio_dev *indio_dev)
+{
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
+
+       meson_sar_adc_stop_sample_engine(indio_dev);
+
+       meson_sar_adc_sample_mode_set(indio_dev, SINGLE_MODE);
+
+       /* disable irq */
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
+                       MESON_SAR_ADC_REG0_FIFO_IRQ_EN, 0);
+
+       /* disable chnl regs */
+       regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11,
+                       MESON_SAR_ADC_REG11_CHNL_REGS_EN, 0);
+       return 0;
+}
+
+static const struct iio_buffer_setup_ops meson_sar_adc_buffer_setup_ops = {
+       .postenable  = meson_sar_adc_buffer_postenable,
+       .predisable = meson_sar_adc_buffer_predisable,
+};
+
 static ssize_t chan7_mux_show(struct device *dev, struct device_attribute *attr,
                        char *buf)
 {
@@ -1019,10 +1285,24 @@ static const struct attribute_group meson_sar_adc_attr_group = {
 static const struct iio_info meson_sar_adc_iio_info = {
        .read_raw = meson_sar_adc_iio_info_read_raw,
        .write_raw = meson_sar_adc_iio_info_write_raw,
+       .update_scan_mode = meson_sar_adc_update_scan_mode,
        .attrs = &meson_sar_adc_attr_group,
        .driver_module = THIS_MODULE,
 };
 
+struct meson_sar_adc_data meson_sar_adc_g12a_data = {
+       .obt_temp_chan6 = false,
+       .has_bl30_integration = false,
+       .period_support = true,
+       .has_chnl_regs = true,
+       .vref_sel = VDDA_AS_VREF,
+       .resolution = SAR_ADC_12BIT,
+       .name = "meson-g12a-saradc",
+       .regs_diff = {
+               .reg3_ring_counter_disable = BIT_HIGH,
+       },
+};
+
 struct meson_sar_adc_data meson_sar_adc_txlx_data = {
        .obt_temp_chan6 = false,
        .has_bl30_integration = true,
@@ -1080,6 +1360,9 @@ struct meson_sar_adc_data meson_sar_adc_m8b_data = {
 
 static const struct of_device_id meson_sar_adc_of_match[] = {
        {
+               .compatible = "amlogic,meson-g12a-saradc",
+               .data = &meson_sar_adc_g12a_data,
+       }, {
                .compatible = "amlogic,meson-txlx-saradc",
                .data = &meson_sar_adc_txlx_data,
        }, {
@@ -1107,9 +1390,7 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
        void __iomem *base;
        const struct of_device_id *match;
        int ret;
-#ifdef MESON_SAR_ADC_IRQ_MODE
        int irq;
-#endif
 
        indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
        if (!indio_dev) {
@@ -1118,9 +1399,6 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
        }
 
        priv = iio_priv(indio_dev);
-#ifdef MESON_SAR_ADC_IRQ_MODE
-       init_completion(&priv->done);
-#endif
        match = of_match_device(meson_sar_adc_of_match, &pdev->dev);
        priv->data = match->data;
 
@@ -1133,20 +1411,17 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
        indio_dev->channels = meson_sar_adc_iio_channels;
        indio_dev->num_channels = ARRAY_SIZE(meson_sar_adc_iio_channels);
 
+       meson_sar_adc_chan_spec_update(indio_dev);
+
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(base))
                return PTR_ERR(base);
-#ifdef MESON_SAR_ADC_IRQ_MODE
+
        irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
        if (!irq)
                return -EINVAL;
 
-       ret = devm_request_irq(&pdev->dev, irq, meson_sar_adc_irq, IRQF_SHARED,
-                              dev_name(&pdev->dev), indio_dev);
-       if (ret)
-               return ret;
-#endif
        priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
                                             &meson_sar_adc_regmap_config);
        if (IS_ERR(priv->regmap))
@@ -1187,6 +1462,36 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
 
        priv->calibscale = MILLION;
 
+       if (priv->data->period_support) {
+               ret = of_property_read_u32(pdev->dev.of_node,
+                       "amlogic,delay-per-tick", &priv->delay_per_tick);
+               if (ret) {
+                       dev_info(&pdev->dev,
+                               "set delay per tick to <1ms> by default.");
+                       /* 1ms per tick */
+                       priv->delay_per_tick = 3;
+               }
+
+               ret = of_property_read_u32(pdev->dev.of_node,
+                       "amlogic,ticks-per-period", &priv->ticks_per_period);
+               if (ret) {
+                       dev_info(&pdev->dev,
+                               "set ticks per period to <1> by default.");
+                       /* 1 ticks per sampling period */
+                       priv->ticks_per_period = 1;
+               }
+
+               ret = meson_sar_adc_iio_buffer_setup(indio_dev,
+                       &meson_sar_adc_worker,
+                       &meson_sar_adc_irq,
+                       irq,
+                       IRQF_SHARED,
+                       &meson_sar_adc_buffer_setup_ops);
+
+               if (ret)
+                       return ret;
+       }
+
        ret = meson_sar_adc_init(indio_dev);
        if (ret)
                goto err;
@@ -1210,14 +1515,19 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
 err_hw:
        meson_sar_adc_hw_disable(indio_dev);
 err:
+       meson_sar_adc_iio_buffer_cleanup(indio_dev);
+
        return ret;
 }
 
 static int meson_sar_adc_remove(struct platform_device *pdev)
 {
        struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+       struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
 
        iio_device_unregister(indio_dev);
+       meson_sar_adc_iio_buffer_cleanup(indio_dev);
+       kfree(priv->datum_buf);
 
        return meson_sar_adc_hw_disable(indio_dev);
 }
@@ -1225,15 +1535,36 @@ static int meson_sar_adc_remove(struct platform_device *pdev)
 static int __maybe_unused meson_sar_adc_suspend(struct device *dev)
 {
        struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       int ret;
 
-       return meson_sar_adc_hw_disable(indio_dev);
+       if (iio_buffer_enabled(indio_dev)) {
+               ret = meson_sar_adc_buffer_predisable(indio_dev);
+               if (ret)
+                       return ret;
+       }
+
+       ret = meson_sar_adc_hw_disable(indio_dev);
+       if (ret)
+               return ret;
+
+       return 0;
 }
 
 static int __maybe_unused meson_sar_adc_resume(struct device *dev)
 {
        struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       int ret;
+
+       ret = meson_sar_adc_hw_enable(indio_dev);
+       if (ret)
+               return ret;
 
-       return meson_sar_adc_hw_enable(indio_dev);
+       if (iio_buffer_enabled(indio_dev)) {
+               ret = meson_sar_adc_buffer_postenable(indio_dev);
+               if (ret)
+                       return ret;
+       }
+       return 0;
 }
 
 static SIMPLE_DEV_PM_OPS(meson_sar_adc_pm_ops,
@@ -1251,6 +1582,6 @@ static struct platform_driver meson_sar_adc_driver = {
 
 module_platform_driver(meson_sar_adc_driver);
 
-MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com> and Amlogic");
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
 MODULE_DESCRIPTION("Amlogic Meson SAR ADC driver");
 MODULE_LICENSE("GPL v2");