guowenxue
2024-07-18 b8d02950d8c50611c2784c7a40e0b3003acf8d49
kernel/patches/igkboard-imx8mp/linux-imx-lf-6.1.36-2.1.0.patch
@@ -10,10 +10,10 @@
+dtb-$(CONFIG_ARCH_MXC) += igkboard-imx8mp.dtb
diff --git a/arch/arm64/boot/dts/freescale/igkboard-imx8mp.dts b/arch/arm64/boot/dts/freescale/igkboard-imx8mp.dts
new file mode 100644
index 000000000..7f3e9a74a
index 000000000..016d92023
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/igkboard-imx8mp.dts
@@ -0,0 +1,613 @@
@@ -0,0 +1,829 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/* 
+ * Device Tree Source for LingYun IGKBoard(IoT Gateway Kits Board) - imx8mp
@@ -105,6 +105,25 @@
+            gpios = <&gpio5 27 GPIO_ACTIVE_LOW>;
+            linux,code = <BTN_4>;
+        };
+    };
+
+    sound-wm8960 {
+        compatible = "fsl,imx-audio-wm8960";
+        model = "wm8960-audio";
+        audio-cpu = <&sai3>;
+        audio-codec = <&codec>;
+        audio-asrc = <&easrc>;
+        //hp-det-gpio = <&gpio4 29 0>;
+        audio-routing =
+            "Headphone Jack", "HP_L",
+            "Headphone Jack", "HP_R",
+            "Ext Spk", "SPK_LP",
+            "Ext Spk", "SPK_LN",
+            "Ext Spk", "SPK_RP",
+            "Ext Spk", "SPK_RN",
+            "LINPUT1", "Mic Jack",
+            "LINPUT3", "Mic Jack",
+            "Mic Jack", "MICB";
+    };
+};
+
@@ -266,6 +285,136 @@
+    status = "okay";
+};
+
+&i2c2 {
+    clock-frequency = <100000>;
+    pinctrl-names = "default";
+    pinctrl-0 = <&pinctrl_i2c2>;
+    status = "okay";
+
+    codec: wm8960@1a {
+        compatible = "wlf,wm8960";
+        reg = <0x1a>;
+        clocks = <&audio_blk_ctrl IMX8MP_CLK_AUDIO_BLK_CTRL_SAI3_MCLK1>;
+        clock-names = "mclk";
+        wlf,shared-lrclk;
+    };
+
+    ms1112@4a {
+        compatible = "ms,ms1112";
+        reg = <0x4a>;
+        status = "okay";
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        channel@2 {
+                reg = <2>;
+                ti,gain = <0>;
+                ti,datarate = <3>;
+                ti,mode = <1>;
+        };
+
+        channel@3{
+                reg = <3>;
+                ti,gain = <0>;
+                ti,datarate = <3>;
+                ti,mode = <1>;
+        };
+    };
+
+    rtc1208@6f {
+        compatible = "isil,isl1208";
+        reg = <0x6f>;
+        status = "okay";
+    };
+};
+
+/*+------------------------+
+  |   WM8960 Audio Codec   |
+  +------------------------+*/
+
+&sai3 {
+    pinctrl-names = "default";
+    pinctrl-0 = <&pinctrl_sai3>;
+    assigned-clocks = <&clk IMX8MP_CLK_SAI3>;
+    assigned-clock-parents = <&clk IMX8MP_AUDIO_PLL1_OUT>;
+    assigned-clock-rates = <12288000>;
+    clocks = <&audio_blk_ctrl IMX8MP_CLK_AUDIO_BLK_CTRL_SAI3_IPG>, <&clk IMX8MP_CLK_DUMMY>,
+         <&audio_blk_ctrl IMX8MP_CLK_AUDIO_BLK_CTRL_SAI3_MCLK1>, <&clk IMX8MP_CLK_DUMMY>,
+         <&clk IMX8MP_CLK_DUMMY>;
+    clock-names = "bus", "mclk0", "mclk1", "mclk2", "mclk3";
+    fsl,sai-mclk-direction-output;
+    status = "okay";
+};
+
+&easrc {
+    fsl,asrc-rate  = <48000>;
+    status = "okay";
+};
+
+&xcvr {
+    #sound-dai-cells = <0>;
+    status = "okay";
+};
+
+&sdma2 {
+    status = "okay";
+};
+
+/*+------------------------+
+  |      HDMI Display      |
+  +------------------------+*/
+
+&irqsteer_hdmi {
+    status = "okay";
+};
+
+&hdmi_blk_ctrl {
+    status = "okay";
+};
+
+&hdmi_pavi {
+    status = "okay";
+};
+
+&hdmi {
+    status = "okay";
+};
+
+&hdmiphy {
+    status = "okay";
+};
+
+&lcdif3 {
+    status = "okay";
+
+    thres-low  = <1 2>;     /* (FIFO * 1 / 2) */
+    thres-high = <3 4>;     /* (FIFO * 3 / 4) */
+};
+
+/*+------------------------+
+  |  CAN/RS485 interface   |
+  +------------------------+*/
+
+/* RS485 */
+&uart3 {
+    pinctrl-names = "default";
+    pinctrl-0 = <&pinctrl_uart3>;
+    status = "okay";
+};
+
+/* CAN */
+&flexcan1 {
+    pinctrl-names = "default";
+    pinctrl-0 = <&pinctrl_flexcan1>;
+    status = "okay";
+};
+
+&flexcan2 {
+    pinctrl-names = "default";
+    pinctrl-0 = <&pinctrl_flexcan2>;
+    status = "okay";
+};
+
+/*+------------------------+
+  |   MikroBUS interface   |
+  +------------------------+*/
@@ -309,6 +458,19 @@
+    pinctrl-names = "default";
+    pinctrl-0 = <&pinctrl_i2c5>;
+    status = "okay";
+
+    hdc1080@40 {
+        compatible = "ti,hdc1080";
+        reg = <0x40>;
+        status = "okay";
+    };
+
+    eeprom@50 {
+        compatible = "microchip,24c32", "atmel,24c32";
+        reg = <0x50>;
+        pagesize = <32>;
+        num-addresses = <8>;
+    };
+};
+
+/*+------------------------+
@@ -419,6 +581,21 @@
+
+&iomuxc {
+    pinctrl-names = "default";
+    pinctrl-0 = <&pinctrl_hog>;
+
+    pinctrl_hog: hoggrp {
+        fsl,pins = <
+            MX8MP_IOMUXC_HDMI_DDC_SCL__HDMIMIX_HDMI_SCL                 0x400001c2
+            MX8MP_IOMUXC_HDMI_DDC_SDA__HDMIMIX_HDMI_SDA                 0x400001c2
+            MX8MP_IOMUXC_HDMI_HPD__HDMIMIX_HDMI_HPD                     0x40000010
+            MX8MP_IOMUXC_HDMI_CEC__HDMIMIX_HDMI_CEC                     0x40000010
+            /*
+             * M.2 pin20 & pin21 need to be set to 11 for 88W9098 to select the
+             * default Reference Clock Frequency
+             */
+            MX8MP_IOMUXC_SD1_DATA7__GPIO2_IO09                          0x1c4
+        >;
+    };
+
+    pinctrl_wdog: wdoggrp {
+        fsl,pins = <
@@ -470,6 +647,27 @@
+        >;
+    };
+
+    pinctrl_uart3: uart3grp {
+        fsl,pins = <
+            MX8MP_IOMUXC_ECSPI1_SCLK__UART3_DCE_RX                      0x82
+            MX8MP_IOMUXC_ECSPI1_MOSI__UART3_DCE_TX                      0x82
+        >;
+    };
+
+    pinctrl_flexcan1: flexcan1grp {
+        fsl,pins = <
+            MX8MP_IOMUXC_SPDIF_TX__CAN1_TX                              0x154
+            MX8MP_IOMUXC_SPDIF_RX__CAN1_RX                              0x154
+        >;
+    };
+
+    pinctrl_flexcan2: flexcan2grp {
+        fsl,pins = <
+            MX8MP_IOMUXC_SAI5_MCLK__CAN2_RX                             0x154
+            MX8MP_IOMUXC_SAI5_RXD3__CAN2_TX                             0x154
+        >;
+    };
+
+    pinctrl_ecspi2: ecspi2grp {
+        fsl,pins = <
+            MX8MP_IOMUXC_ECSPI2_SCLK__ECSPI2_SCLK                       0x82
@@ -486,10 +684,28 @@
+        >;
+    };
+
+    pinctrl_i2c2: i2c2grp {
+        fsl,pins = <
+            MX8MP_IOMUXC_I2C2_SCL__I2C2_SCL                             0x400001c2
+            MX8MP_IOMUXC_I2C2_SDA__I2C2_SDA                             0x400001c2
+        >;
+    };
+
+    pinctrl_i2c5: i2c5grp {
+        fsl,pins = <
+            MX8MP_IOMUXC_SD1_CMD__I2C5_SDA                              0x400001c2
+            MX8MP_IOMUXC_SD1_CLK__I2C5_SCL                              0x400001c2
+        >;
+    };
+
+    pinctrl_sai3: sai3grp {
+        fsl,pins = <
+            MX8MP_IOMUXC_SAI3_TXFS__AUDIOMIX_SAI3_TX_SYNC               0xd6
+            MX8MP_IOMUXC_SAI3_TXC__AUDIOMIX_SAI3_TX_BCLK                0xd6
+            MX8MP_IOMUXC_SAI3_RXD__AUDIOMIX_SAI3_RX_DATA00              0xd6
+            MX8MP_IOMUXC_SAI3_TXD__AUDIOMIX_SAI3_TX_DATA00              0xd6
+            MX8MP_IOMUXC_SAI3_MCLK__AUDIOMIX_SAI3_MCLK                  0xd6
+            MX8MP_IOMUXC_SAI3_RXC__GPIO4_IO29                           0xd6
+        >;
+    };
+
@@ -629,10 +845,10 @@
+};
diff --git a/arch/arm64/configs/igkboard-imx8mp_defconfig b/arch/arm64/configs/igkboard-imx8mp_defconfig
new file mode 100644
index 000000000..d6d6206f2
index 000000000..b0f923742
--- /dev/null
+++ b/arch/arm64/configs/igkboard-imx8mp_defconfig
@@ -0,0 +1,1129 @@
@@ -0,0 +1,1103 @@
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_AUDIT=y
@@ -796,7 +1012,8 @@
+CONFIG_QRTR_SMD=m
+CONFIG_QRTR_TUN=m
+CONFIG_NET_PKTGEN=m
+CONFIG_CAN=m
+CONFIG_CAN=y
+CONFIG_CAN_ISOTP=y
+CONFIG_BT=y
+CONFIG_BT_RFCOMM=y
+CONFIG_BT_RFCOMM_TTY=y
@@ -821,8 +1038,6 @@
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=y
+CONFIG_MAC80211_LEDS=y
+CONFIG_NET_9P=y
+CONFIG_NET_9P_VIRTIO=y
+CONFIG_NFC=m
+CONFIG_NFC_NCI=m
+CONFIG_NFC_S3FWRN5_I2C=m
@@ -891,8 +1106,7 @@
+CONFIG_BLK_DEV_NVME=y
+CONFIG_SRAM=y
+CONFIG_PCI_ENDPOINT_TEST=y
+CONFIG_EEPROM_AT24=m
+CONFIG_EEPROM_AT25=m
+CONFIG_EEPROM_AT24=y
+CONFIG_UACCE=m
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
@@ -980,7 +1194,7 @@
+CONFIG_REALTEK_PHY=y
+CONFIG_ROCKCHIP_PHY=y
+CONFIG_VITESSE_PHY=y
+CONFIG_CAN_FLEXCAN=m
+CONFIG_CAN_FLEXCAN=y
+CONFIG_MDIO_BITBANG=y
+CONFIG_MDIO_BUS_MUX_MULTIPLEXER=y
+CONFIG_MDIO_BUS_MUX_MMIOREG=y
@@ -1463,27 +1677,7 @@
+CONFIG_EDAC_LAYERSCAPE=m
+CONFIG_EDAC_SYNOPSYS=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_DS1307=m
+CONFIG_RTC_DRV_HYM8563=m
+CONFIG_RTC_DRV_MAX77686=y
+CONFIG_RTC_DRV_RK808=m
+CONFIG_RTC_DRV_PCF85363=m
+CONFIG_RTC_DRV_M41T80=m
+CONFIG_RTC_DRV_RX8581=m
+CONFIG_RTC_DRV_RV3028=m
+CONFIG_RTC_DRV_RV8803=m
+CONFIG_RTC_DRV_S5M=y
+CONFIG_RTC_DRV_DS3232=y
+CONFIG_RTC_DRV_PCF2127=m
+CONFIG_RTC_DRV_PCF2131=m
+CONFIG_RTC_DRV_EFI=y
+CONFIG_RTC_DRV_CROS_EC=y
+CONFIG_RTC_DRV_FSL_FTM_ALARM=m
+CONFIG_RTC_DRV_PL031=y
+CONFIG_RTC_DRV_SNVS=y
+CONFIG_RTC_DRV_BBNSM=y
+CONFIG_RTC_DRV_IMX_SC=y
+CONFIG_RTC_DRV_IMX_RPMSG=y
+CONFIG_RTC_DRV_ISL1208=y
+CONFIG_DMADEVICES=y
+CONFIG_FSL_EDMA=y
+CONFIG_FSL_QDMA=m
@@ -1575,23 +1769,20 @@
+CONFIG_IIO_ST_ACCEL_3AXIS=m
+CONFIG_IMX8QXP_ADC=y
+CONFIG_IMX93_ADC=y
+CONFIG_MAX9611=m
+CONFIG_QCOM_SPMI_VADC=m
+CONFIG_QCOM_SPMI_ADC5=m
+CONFIG_IIO_CROS_EC_SENSORS_CORE=m
+CONFIG_IIO_CROS_EC_SENSORS=m
+CONFIG_FXAS21002C=y
+CONFIG_MS1112=y
+CONFIG_BMG160=m
+CONFIG_IIO_ST_GYRO_3AXIS=m
+CONFIG_MAX30100=m
+CONFIG_MAX30102=m
+CONFIG_DHT11=y
+CONFIG_HDC100X=y
+CONFIG_HTS221=y
+CONFIG_FXOS8700_I2C=y
+CONFIG_RPMSG_IIO_PEDOMETER=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_IIO_ST_LSM6DSX=y
+CONFIG_IIO_CROS_EC_LIGHT_PROX=m
+CONFIG_SENSORS_ISL29018=y
+CONFIG_VCNL4000=m
+CONFIG_VCNL4035=m
+CONFIG_IIO_ST_MAGN_3AXIS=m
+CONFIG_IIO_CROS_EC_BARO=m
+CONFIG_MPL3115=y
+CONFIG_MS5611=m
+CONFIG_MS5611_I2C=m
@@ -1669,7 +1860,6 @@
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_9P_FS=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_936=y
+CONFIG_NLS_CODEPAGE_950=y
@@ -1762,3 +1952,613 @@
+CONFIG_CORESIGHT_CPU_DEBUG=m
+CONFIG_CORESIGHT_CTI=m
+CONFIG_MEMTEST=y
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 3946eb595..15278c13c 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1164,6 +1164,18 @@ config TI_ADC161S626
      This driver can also be built as a module. If so, the module will be
      called ti-adc161s626.
+config MS1112
+   tristate "Ruimeng Technology MS1112 ADC"
+   depends on I2C
+   select IIO_BUFFER
+   select IIO_TRIGGERED_BUFFER
+   help
+     If you say yes here you get support for Ruimeng Technology ADS1015
+     ADC chip.
+
+     This driver can also be built as a module. If so, the module will be
+     called ms1112.
+
 config TI_ADS1015
    tristate "Texas Instruments ADS1015 ADC"
    depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 83233c38c..f403164cf 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -104,6 +104,7 @@ obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
 obj-$(CONFIG_TI_ADC108S102) += ti-adc108s102.o
 obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
 obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o
+obj-$(CONFIG_MS1112) += ms1112.o
 obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o
 obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o
 obj-$(CONFIG_TI_ADS8344) += ti-ads8344.o
diff --git a/drivers/iio/adc/ms1112.c b/drivers/iio/adc/ms1112.c
new file mode 100644
index 000000000..cf8ea5c66
--- /dev/null
+++ b/drivers/iio/adc/ms1112.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MS1112 - Ruimeng Technology Analog-to-Digital Converter
+ *
+ * Copyright (c) 2024, LingYun IoT System Studio.
+ *
+ * IIO driver for MS1112 ADC 7-bit I2C slave address: 0x4A
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/of_gpio.h>
+#include <linux/semaphore.h>
+#include <linux/timer.h>
+#include <linux/i2c.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/driver.h>
+
+#define MS1112_DRV_NAME            "ms1112"
+
+#define MS1112_CONV_REG            0x00
+#define MS1112_CFG_REG            0x01
+#define MS1112_DEFAULT_CONFIG      0xFC
+
+#define MS1112_CHANNELS            4
+#define MS1112_CFG_DR_SHIFT         2
+#define MS1112_CFG_MOD_SHIFT      4
+#define MS1112_CFG_PGA_SHIFT      0
+#define MS1112_CFG_MUX_SHIFT      5
+
+#define MS1112_CFG_DR_MASK         GENMASK(3, 2)
+#define MS1112_CFG_MOD_MASK         BIT(4)
+#define MS1112_CFG_PGA_MASK         GENMASK(1, 0)
+#define MS1112_CFG_MUX_MASK         GENMASK(6, 5)
+
+#define MS1112_DEFAULT_PGA         0
+#define MS1112_DEFAULT_DATA_RATE   3
+#define MS1112_DEFAULT_CHAN         2
+#define MS1112_DEFAULT_MODE         1
+
+#define MS1112_CONTINUOUS         0
+#define MS1112_SINGLESHOT         1
+
+struct ms1112_chip_data {
+   struct iio_chan_spec const   *channels;
+   int                     num_channels;
+   const struct iio_info      *info;
+   const int               *data_rate;
+   const int               data_rate_len;
+   const int               *scale;
+   const int               scale_len;
+   bool                  has_comparator;
+};
+
+enum ms1112_channels {
+   MS1112_AIN0_AIN1 = 0,
+   MS1112_AIN2,
+   MS1112_AIN0,
+   MS1112_AIN1,
+   MS1112_TIMESTAMP,
+};
+
+static const int ms1112_data_rate[] = {
+   240,60,30,15
+};
+
+static const int ms1112_fullscale_range[] = {
+   2048
+};
+
+static const int ms1112_scale[] = { /* 12bit ADC */
+   2048,11,
+   2048,13,
+   2048,14,
+   2048,15
+};
+
+#define FIT_CHECK(_testbits, _fitbits)            \
+   (                                    \
+      (_fitbits) *                        \
+      !!sizeof(struct {                     \
+      static_assert((_testbits) <= (_fitbits));   \
+      int pad;                           \
+      })                                 \
+   )
+
+#define MS1112_V_CHAN(_chan, _addr, _realbits, _shift, _event_spec, _num_event_specs) { \
+   .type = IIO_VOLTAGE,            \
+   .indexed = 1,                  \
+   .address = _addr,               \
+   .channel = _chan,               \
+   .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |   \
+   BIT(IIO_CHAN_INFO_SCALE) |                  \
+   BIT(IIO_CHAN_INFO_SAMP_FREQ),               \
+   .info_mask_shared_by_all_available =         \
+   BIT(IIO_CHAN_INFO_SCALE) |                  \
+   BIT(IIO_CHAN_INFO_SAMP_FREQ),               \
+   .scan_index = _addr,                     \
+   .scan_type = {                           \
+      .sign = 's',                        \
+      .realbits = (_realbits),               \
+      .storagebits = FIT_CHECK((_realbits) + (_shift), 16),   \
+      .shift = (_shift),                     \
+      .endianness = IIO_CPU,                  \
+   },                                    \
+   .event_spec = (_event_spec),               \
+   .num_event_specs = (_num_event_specs),         \
+   .datasheet_name = "AIN"#_chan,               \
+}
+
+#define MS1112_V_DIFF_CHAN(_chan, _chan2, _addr, _realbits, _shift, _event_spec, _num_event_specs) { \
+   .type = IIO_VOLTAGE,                     \
+   .differential = 1,                        \
+   .indexed = 1,                           \
+   .address = _addr,                        \
+   .channel = _chan,                        \
+   .channel2 = _chan2,                        \
+   .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |   \
+   BIT(IIO_CHAN_INFO_SCALE) |                  \
+   BIT(IIO_CHAN_INFO_SAMP_FREQ),               \
+   .info_mask_shared_by_all_available =         \
+   BIT(IIO_CHAN_INFO_SCALE) |                  \
+   BIT(IIO_CHAN_INFO_SAMP_FREQ),               \
+   .scan_index = _addr,                     \
+   .scan_type = {                           \
+      .sign = 's',                        \
+      .realbits = (_realbits),               \
+      .storagebits = FIT_CHECK((_realbits) + (_shift), 16),   \
+      .shift = (_shift),                     \
+      .endianness = IIO_CPU,                  \
+   },                                    \
+   .event_spec = (_event_spec),               \
+   .num_event_specs = (_num_event_specs),         \
+   .datasheet_name = "AIN"#_chan"-AIN"#_chan2,      \
+}
+
+struct ms1112_channel_data {
+   bool enabled;
+   unsigned int pga;
+   unsigned int data_rate;
+   unsigned int mode;
+};
+
+struct ms1112_thresh_data {
+   int high_thresh;
+   int low_thresh;
+};
+
+struct ms1112_data {
+   struct ms1112_channel_data channel_data[MS1112_CHANNELS];
+   struct ms1112_thresh_data thresh_data[MS1112_CHANNELS];
+   const struct ms1112_chip_data *chip;
+   struct mutex lock;
+   void *private_data;
+   struct i2c_client *client;
+};
+
+/* MS1112 don't use the register address */
+static int ms1112_read_regs(struct ms1112_data *dev, uint8_t reg, void *buf, uint8_t size)
+{
+   int               ret = 0;
+   struct i2c_msg      msg[1];
+   struct i2c_client   *client = dev->client;
+
+   msg[0].addr  = client->addr;
+   msg[0].flags = I2C_M_RD;
+   msg[0].buf    = buf;
+   msg[0].len    = size;
+
+   ret = i2c_transfer(client->adapter, msg, 1);
+   if(ret != 1) {
+      dev_err(&dev->client->dev, "%s() i2c_transfer error, ret=%d\n", __func__, ret);
+      ret = -EREMOTEIO;
+   }
+
+   return ret;
+}
+
+/* MS1112 don't use the register address */
+static s32 ms1112_write_regs(struct ms1112_data *dev, uint8_t reg, uint8_t *data, uint8_t bytes)
+{
+   int               ret = 0;
+   struct i2c_msg      msg;
+   struct i2c_client   *client = dev->client;
+
+   msg.addr  = client->addr;
+   msg.flags = 0;
+   msg.buf   = data;
+   msg.len   = bytes;
+
+   ret = i2c_transfer(client->adapter, &msg, 1);
+   if(ret != 1) {
+      dev_err(&dev->client->dev, "%s() i2c_transfer error, ret=%d\n", __func__, ret);
+      ret = -EREMOTEIO;
+   }
+
+   return ret;
+}
+
+static int ms1112_readdata(struct ms1112_data *dev,unsigned int *val)
+{
+   unsigned char   buf[3];
+   unsigned char   rx_data[3];
+   int            rv = 0;
+
+   rv = ms1112_read_regs(dev, MS1112_CONV_REG, rx_data, 3);
+   if(rv<0) {
+      return rv;
+   }
+
+   buf[0] = rx_data[0];
+   buf[1] = rx_data[1];
+   buf[2] = rx_data[2];
+
+   *val = (buf[0]<<8) | buf[1];
+   return rv;
+}
+
+static int ms1112_get_adc_result(struct ms1112_data *data, int chan, int *val)
+{
+   int         ret = 0;
+   int         pga, dr , mode;
+   uint8_t      mask, cfg;
+
+   if (chan < 0 || chan >= MS1112_CHANNELS)
+      return -EINVAL;
+
+   mode = data->channel_data[chan].mode;
+   pga = data->channel_data[chan].pga;
+   dr = data->channel_data[chan].data_rate;
+
+   mask = MS1112_CFG_MUX_MASK | MS1112_CFG_PGA_MASK |
+      MS1112_CFG_DR_MASK | MS1112_CFG_MOD_MASK | MS1112_SINGLESHOT << 7;
+
+   cfg = chan << MS1112_CFG_MUX_SHIFT | pga << MS1112_CFG_PGA_SHIFT |
+      dr << MS1112_CFG_DR_SHIFT | mode << MS1112_CFG_MOD_SHIFT | MS1112_SINGLESHOT << 7;
+
+   cfg = (cfg & mask);
+
+   ms1112_write_regs(data, MS1112_CFG_REG, &cfg, 1);
+
+   ret = ms1112_readdata(data,val);
+   return ret;
+}
+
+static int ms1112_set_scale(struct ms1112_data *data, struct iio_chan_spec const *chan,
+      int scale, int uscale)
+{
+   int i;
+   int fullscale = div_s64((scale * 1000000LL + uscale) <<
+         (chan->scan_type.realbits - 1), 1000000);
+
+   for (i = 0; i < ARRAY_SIZE(ms1112_fullscale_range); i++) {
+      if (ms1112_fullscale_range[i] == fullscale) {
+         data->channel_data[chan->address].pga = i;
+         return 0;
+      }
+   }
+
+   return -EINVAL;
+}
+
+static int ms1112_set_data_rate(struct ms1112_data *data, int chan, int rate)
+{
+   int i;
+
+   for (i = 0; i < data->chip->data_rate_len; i++) {
+      if (data->chip->data_rate[i] == rate) {
+         data->channel_data[chan].data_rate = i;
+         return 0;
+      }
+   }
+
+   return -EINVAL;
+}
+
+static int ms1112_read_avail(struct iio_dev *indio_dev,
+      struct iio_chan_spec const *chan,
+      const int **vals, int *type, int *length,
+      long mask)
+{
+   struct ms1112_data *data = iio_priv(indio_dev);
+
+   if (chan->type != IIO_VOLTAGE)
+      return -EINVAL;
+
+   switch (mask) {
+      case IIO_CHAN_INFO_SCALE:
+         *type = IIO_VAL_FRACTIONAL_LOG2;
+         *vals =  data->chip->scale;
+         *length = data->chip->scale_len;
+         return IIO_AVAIL_LIST;
+      case IIO_CHAN_INFO_SAMP_FREQ:
+         *type = IIO_VAL_INT;
+         *vals = data->chip->data_rate;
+         *length = data->chip->data_rate_len;
+         return IIO_AVAIL_LIST;
+      default:
+         return -EINVAL;
+   }
+}
+
+static int ms1112_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+   int ret, idx;
+   struct ms1112_data *data = iio_priv(indio_dev);
+
+   mutex_lock(&data->lock);
+   switch (mask) {
+      case IIO_CHAN_INFO_RAW:
+
+         ret = iio_device_claim_direct_mode(indio_dev);
+         if (ret)
+            break;
+
+         ret = ms1112_get_adc_result(data, chan->address, val);
+         if (ret < 0) {
+            goto release_direct;
+         }
+
+         *val = sign_extend32(*val >> chan->scan_type.shift,
+               chan->scan_type.realbits - 1);
+
+         ret = IIO_VAL_INT;
+release_direct:
+         iio_device_release_direct_mode(indio_dev);
+         break;
+
+      case IIO_CHAN_INFO_SCALE:
+         idx = data->channel_data[chan->address].pga;
+         *val = ms1112_fullscale_range[idx];
+         *val2 = chan->scan_type.realbits - 1;
+         ret = IIO_VAL_FRACTIONAL_LOG2;
+         break;
+      case IIO_CHAN_INFO_SAMP_FREQ:
+         idx = data->channel_data[chan->address].data_rate;
+         *val = data->chip->data_rate[idx];
+         ret = IIO_VAL_INT;
+         break;
+      default:
+         ret = -EINVAL;
+         break;
+   }
+   mutex_unlock(&data->lock);
+
+   return ret;
+}
+
+static int ms1112_write_raw(struct iio_dev *indio_dev,
+      struct iio_chan_spec const *chan, int val,
+      int val2, long mask)
+{
+   struct ms1112_data *data = iio_priv(indio_dev);
+   int ret;
+
+   mutex_lock(&data->lock);
+   switch (mask) {
+      case IIO_CHAN_INFO_SCALE:
+         ret = ms1112_set_scale(data, chan, val, val2);
+         break;
+      case IIO_CHAN_INFO_SAMP_FREQ:
+         ret = ms1112_set_data_rate(data, chan->address, val);
+         break;
+      default:
+         ret = -EINVAL;
+         break;
+   }
+   mutex_unlock(&data->lock);
+
+   return ret;
+}
+
+static const struct iio_info ms1112_info = {
+   .read_raw = ms1112_read_raw,
+   .write_raw = ms1112_write_raw,
+   .read_avail = ms1112_read_avail,
+};
+
+
+static const struct iio_chan_spec ms1112_channels[] = {
+   MS1112_V_DIFF_CHAN(0, 1, MS1112_AIN0_AIN1, 16, 0, NULL, 0),
+   MS1112_V_CHAN(2, MS1112_AIN2, 16, 0, NULL, 0),
+   MS1112_V_CHAN(0, MS1112_AIN0, 16, 0, NULL, 0),
+   MS1112_V_CHAN(1, MS1112_AIN1, 16, 0, NULL, 0),
+   IIO_CHAN_SOFT_TIMESTAMP(MS1112_TIMESTAMP),
+};
+
+static int ms1112_client_get_channels_config(struct i2c_client *client)
+{
+   struct iio_dev *indio_dev = i2c_get_clientdata(client);
+   struct ms1112_data *data = iio_priv(indio_dev);
+   struct device *dev = &client->dev;
+   struct fwnode_handle *node;
+   int i = -1;
+
+   device_for_each_child_node(dev, node) {
+      u32 pval;
+      unsigned int channel;
+      unsigned int pga = MS1112_DEFAULT_PGA;
+      unsigned int data_rate = MS1112_DEFAULT_DATA_RATE;
+      unsigned int mode = MS1112_DEFAULT_MODE;
+
+      if (fwnode_property_read_u32(node, "reg", &pval)) {
+         dev_err(dev, "invalid reg on %pfw\n", node);
+         continue;
+      }
+
+      channel = pval;
+      if (channel >= MS1112_CHANNELS) {
+         dev_err(dev, "invalid channel index %d on %pfw\n",
+               channel, node);
+         continue;
+      }
+
+      if (!fwnode_property_read_u32(node, "ti,gain", &pval)) {
+         pga = pval;
+         if (pga > 3 ) {
+            dev_err(dev, "invalid gain on %pfw\n", node);
+            fwnode_handle_put(node);
+            return -EINVAL;
+         }
+      }
+
+      if (!fwnode_property_read_u32(node, "ti,datarate", &pval)) {
+         data_rate = pval;
+         if (data_rate > 3) {
+            dev_err(dev, "invalid data_rate on %pfw\n", node);
+            fwnode_handle_put(node);
+            return -EINVAL;
+         }
+      }
+
+      if (!fwnode_property_read_u32(node, "ti,mode", &pval)) {
+         mode = pval;
+         if (mode > 1) {
+            dev_err(dev, "invalid mode on %pfw\n", node);
+            fwnode_handle_put(node);
+            return -EINVAL;
+         }
+      }
+
+
+      data->channel_data[channel].pga = pga;
+      data->channel_data[channel].data_rate = data_rate;
+      data->channel_data[channel].mode = mode;
+      i++;
+   }
+
+   return i < 0 ? -EINVAL : 0;
+}
+
+static void ms1112_get_channels_config(struct i2c_client *client)
+{
+   unsigned int k;
+
+   struct iio_dev *indio_dev = i2c_get_clientdata(client);
+   struct ms1112_data *data = iio_priv(indio_dev);
+
+   if (!ms1112_client_get_channels_config(client))
+      return;
+
+   /* fallback on default configuration */
+   for (k = 0; k < MS1112_CHANNELS; ++k) {
+      data->channel_data[k].pga = MS1112_DEFAULT_PGA;
+      data->channel_data[k].data_rate = MS1112_DEFAULT_DATA_RATE;
+      data->channel_data[k].mode = MS1112_DEFAULT_MODE;
+   }
+}
+
+static int ms1112_probe(struct i2c_client *client,const struct i2c_device_id *id)
+{
+   struct iio_dev *indio_dev;
+   const struct ms1112_chip_data *chip;
+   struct ms1112_data *data;
+   int ret;
+   int i;
+
+   chip = device_get_match_data(&client->dev);
+   if (!chip)
+      chip = (const struct ms1112_chip_data *)id->driver_data;
+   if (!chip)
+      return dev_err_probe(&client->dev, -EINVAL, "Unknown chip\n");
+
+   indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*indio_dev));
+   if (!indio_dev)
+      return -ENOMEM;
+
+   data = iio_priv(indio_dev);
+   i2c_set_clientdata(client, indio_dev);
+
+   mutex_init(&data->lock);
+
+   indio_dev->name = MS1112_DRV_NAME;
+   indio_dev->info = chip->info;
+   indio_dev->modes = INDIO_DIRECT_MODE;
+   indio_dev->channels = chip->channels;
+   indio_dev->num_channels = chip->num_channels;
+   data->chip = chip;
+   data->client = client;
+
+   for (i = 0; i < MS1112_CHANNELS; i++) {
+      int realbits = indio_dev->channels[i].scan_type.realbits;
+
+      data->thresh_data[i].low_thresh = -1 << (realbits - 1);
+      data->thresh_data[i].high_thresh = (1 << (realbits - 1)) - 1;
+   }
+
+   /* we need to keep this ABI the same as used by hwmon ADS1015 driver */
+   ms1112_get_channels_config(client);
+
+   ret = iio_device_register(indio_dev);
+   if (ret)
+      dev_err(&client->dev, "Failed to register IIO device\n");
+   return ret;
+}
+
+static void ms1112_remove(struct i2c_client *client)
+{
+   struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+   iio_device_unregister(indio_dev);
+
+}
+
+static const struct ms1112_chip_data ms1112_data = {
+   .channels   = ms1112_channels,
+   .num_channels   = ARRAY_SIZE(ms1112_channels),
+   .info      = &ms1112_info,
+   .data_rate   = ms1112_data_rate,
+   .data_rate_len   = ARRAY_SIZE(ms1112_data_rate),
+   .scale      = ms1112_scale,
+   .scale_len   = ARRAY_SIZE(ms1112_scale),
+   .has_comparator = false,
+};
+
+static const struct i2c_device_id ms1112_id[] = {
+   { "ms1112", (kernel_ulong_t)&ms1112_data },
+   {}
+};
+MODULE_DEVICE_TABLE(i2c, ms1112_id);
+
+static const struct of_device_id ms1112_of_match[] = {
+   { .compatible = "ms,ms1112" },
+   { },
+};
+MODULE_DEVICE_TABLE(of, ms1112_of_match);
+
+static struct i2c_driver ms1112_driver = {
+   .driver = {
+      .owner = THIS_MODULE,
+      .name = "ms1112",
+      .of_match_table = ms1112_of_match,
+   },
+   .probe = ms1112_probe,
+   .remove = ms1112_remove,
+   .id_table   = ms1112_id,
+};
+
+module_i2c_driver(ms1112_driver);
+
+MODULE_AUTHOR("Tang Junfeng");
+MODULE_DESCRIPTION("MS1112 IIO ADC Driver");
+MODULE_LICENSE("GPL");