From c6d260b5008cd38e7dbda0c6f61489d6dfb286c5 Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Sat, 20 Jul 2024 16:38:05 +0800
Subject: [PATCH] Patch:IGKBoard-IMX8MP: Add OV5640 camera support

---
 kernel/patches/igkboard-imx8mp/linux-imx-lf-6.1.36-2.1.0.patch | 1012 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 966 insertions(+), 46 deletions(-)

diff --git a/kernel/patches/igkboard-imx8mp/linux-imx-lf-6.1.36-2.1.0.patch b/kernel/patches/igkboard-imx8mp/linux-imx-lf-6.1.36-2.1.0.patch
index 35b1422..11a82e4 100644
--- a/kernel/patches/igkboard-imx8mp/linux-imx-lf-6.1.36-2.1.0.patch
+++ b/kernel/patches/igkboard-imx8mp/linux-imx-lf-6.1.36-2.1.0.patch
@@ -10,12 +10,12 @@
 +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..fa0ecba89
+index 000000000..31490ac6a
 --- /dev/null
 +++ b/arch/arm64/boot/dts/freescale/igkboard-imx8mp.dts
-@@ -0,0 +1,657 @@
+@@ -0,0 +1,991 @@
 +// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
-+/* 
++/*
 + * Device Tree Source for LingYun IGKBoard(IoT Gateway Kits Board) - imx8mp
 + * Copyright 2023 LingYun IoT System Studio.
 + */
@@ -106,6 +106,56 @@
 +            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";
++    };
++
++    lvds0_panel {
++        compatible = "boe,ev121wxm-n10-1850";
++        backlight = <&lvds_backlight>;
++
++        port {
++            panel_lvds_in: endpoint {
++                remote-endpoint = <&lvds_out>;
++            };
++        };
++    };
++
++    lvds_backlight: lvds_backlight {
++        compatible = "pwm-backlight";
++        pwms = <&pwm2 0 100000 0>;
++        status = "okay";
++        enable-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
++        brightness-levels = < 0  1  2  3  4  5  6  7  8  9
++                     10 11 12 13 14 15 16 17 18 19
++                     20 21 22 23 24 25 26 27 28 29
++                     30 31 32 33 34 35 36 37 38 39
++                     40 41 42 43 44 45 46 47 48 49
++                     50 51 52 53 54 55 56 57 58 59
++                     60 61 62 63 64 65 66 67 68 69
++                     70 71 72 73 74 75 76 77 78 79
++                     80 81 82 83 84 85 86 87 88 89
++                     90 91 92 93 94 95 96 97 98 99
++                    100>;
++        default-brightness-level = <80>;
++    };
++
 +};
 +
 +/*+------------------------+
@@ -266,9 +316,233 @@
 +    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;
++    };
++
++    ov5640_0: ov5640_mipi@3c {
++        compatible = "ovti,ov5640";
++        reg = <0x3c>;
++        pinctrl-names = "default";
++        pinctrl-0 = <&pinctrl_csi0>;
++        clocks = <&clk IMX8MP_CLK_IPP_DO_CLKO2>;
++        clock-names = "xclk";
++        assigned-clocks = <&clk IMX8MP_CLK_IPP_DO_CLKO2>;
++        assigned-clock-parents = <&clk IMX8MP_CLK_24M>;
++        assigned-clock-rates = <24000000>;
++        csi_id = <0>;
++        powerdown-gpios = <&gpio2 11 GPIO_ACTIVE_HIGH>;
++        reset-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
++        mclk = <24000000>;
++        mclk_source = <0>;
++        mipi_csi;
++        status = "okay";
++
++        port {
++            ov5640_mipi_0_ep: endpoint {
++                remote-endpoint = <&mipi_csi0_ep>;
++                data-lanes = <1 2>;
++                clock-lanes = <0>;
++            };
++        };
++    };
++
++    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";
++};
++
++/*+------------------------+
++  | MIPI-CSI OV5640 Camera |
++  +------------------------+*/
++
++&mipi_csi_0 {
++    #address-cells = <1>;
++    #size-cells = <0>;
++    status = "okay";
++
++    port@0 {
++        reg = <0>;
++        mipi_csi0_ep: endpoint {
++            remote-endpoint = <&ov5640_mipi_0_ep>;
++            data-lanes = <2>;
++            csis-hs-settle = <13>;
++            csis-clk-settle = <2>;
++            csis-wclk;
++        };
++    };
++};
++
++&vpu_g1 {
++    status = "okay";
++};
++
++&vpu_g2 {
++    status = "okay";
++};
++
++&vpu_vc8000e {
++    status = "okay";
++};
++
++&vpu_v4l2 {
++    status = "okay";
++};
++
++&cameradev {
++    status = "okay";
++};
++
++&isi_0 {
++    status = "okay";
++
++    cap_device {
++        status = "okay";
++    };
++
++    m2m_device {
++        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) */
++};
++
++/*+------------------------+
++  |      LVDS Display      |
++  +------------------------+*/
++
++&pwm2 {
++    pinctrl-names = "default";
++    pinctrl-0 = <&pinctrl_pwm2>;
++    status = "okay";
++};
++
++&lcdif2 {
++    status = "okay";
++};
++
++&ldb {
++    status = "okay";
++
++    lvds-channel@0 {
++        fsl,data-mapping = "spwg";
++        fsl,data-width = <24>;
++        status = "okay";
++
++        /delete-node/ port@1;
++        port@1 {
++            reg = <1>;
++
++            lvds_out: endpoint {
++                remote-endpoint = <&panel_lvds_in>;
++            };
++        };
++    };
++};
++
++&ldb_phy {
++    status = "okay";
++};
++
 +/*+------------------------+
 +  |  CAN/RS485 interface   |
 +  +------------------------+*/
++
 +/* RS485 */
 +&uart3 {
 +    pinctrl-names = "default";
@@ -332,6 +606,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>;
++    };
 +};
 +
 +/*+------------------------+
@@ -442,6 +729,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 = <
@@ -467,33 +769,39 @@
 +        >;
 +    };
 +
-+    pinctrl_pwm1: pwm1grp {
++    pinctrl_pwm1: pwm1grp { /* Buzzer */
 +        fsl,pins = <
 +            MX8MP_IOMUXC_GPIO1_IO08__PWM1_OUT                           0x116
 +        >;
 +    };
 +
-+    pinctrl_pwm3: pwm3grp {
++    pinctrl_pwm2: pwm2grp { /* LVDS */
++        fsl,pins = <
++            MX8MP_IOMUXC_GPIO1_IO11__PWM2_OUT   0x116
++        >;
++    };
++
++    pinctrl_pwm3: pwm3grp { /* RPi#40Pin and MikroBUS */
 +        fsl,pins = <
 +            MX8MP_IOMUXC_SAI5_RXC__PWM3_OUT                             0x116
 +        >;
 +    };
 +
-+    pinctrl_uart1: uart1grp {
++    pinctrl_uart1: uart1grp { /* RPi#40Pin and MikroBUS */
 +        fsl,pins = <
 +            MX8MP_IOMUXC_UART1_RXD__UART1_DCE_RX                        0x140
 +            MX8MP_IOMUXC_UART1_TXD__UART1_DCE_TX                        0x140
 +        >;
 +    };
 +
-+    pinctrl_uart2: uart2grp {
++    pinctrl_uart2: uart2grp { /* Console */
 +        fsl,pins = <
 +            MX8MP_IOMUXC_UART2_RXD__UART2_DCE_RX                        0x49
 +            MX8MP_IOMUXC_UART2_TXD__UART2_DCE_TX                        0x49
 +        >;
 +    };
 +
-+    pinctrl_uart3: uart3grp {
++    pinctrl_uart3: uart3grp { /* RS485 */
 +        fsl,pins = <
 +            MX8MP_IOMUXC_ECSPI1_SCLK__UART3_DCE_RX                      0x82
 +            MX8MP_IOMUXC_ECSPI1_MOSI__UART3_DCE_TX                      0x82
@@ -514,7 +822,7 @@
 +        >;
 +    };
 +
-+    pinctrl_ecspi2: ecspi2grp {
++    pinctrl_ecspi2: ecspi2grp { /* RPi#40Pin and MikroBUS */
 +        fsl,pins = <
 +            MX8MP_IOMUXC_ECSPI2_SCLK__ECSPI2_SCLK                       0x82
 +            MX8MP_IOMUXC_ECSPI2_MOSI__ECSPI2_MOSI                       0x82
@@ -523,17 +831,43 @@
 +        >;
 +    };
 +
-+    pinctrl_i2c1: i2c1grp {
++    pinctrl_i2c1: i2c1grp { /* PMIC */
 +        fsl,pins = <
 +            MX8MP_IOMUXC_I2C1_SCL__I2C1_SCL                             0x400001c3
 +            MX8MP_IOMUXC_I2C1_SDA__I2C1_SDA                             0x400001c3
 +        >;
 +    };
 +
-+    pinctrl_i2c5: i2c5grp {
++    pinctrl_i2c2: i2c2grp { /* WM8960, MS1112, ISL1208 */
++        fsl,pins = <
++            MX8MP_IOMUXC_I2C2_SCL__I2C2_SCL                             0x400001c2
++            MX8MP_IOMUXC_I2C2_SDA__I2C2_SDA                             0x400001c2
++        >;
++    };
++
++    pinctrl_i2c5: i2c5grp { /* RPi#40Pin and MikroBUS, HDC1080, AT24C32 */
 +        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
++        >;
++    };
++
++    pinctrl_csi0: csi0_grp {
++         fsl,pins = <
++            MX8MP_IOMUXC_SD1_STROBE__GPIO2_IO11                         0x10 /* PWN */
++            MX8MP_IOMUXC_GPIO1_IO06__GPIO1_IO06                         0x10 /* RST */
++            MX8MP_IOMUXC_GPIO1_IO15__CCM_CLKO2                          0x50 /* MCLK */
 +        >;
 +    };
 +
@@ -673,10 +1007,10 @@
 +};
 diff --git a/arch/arm64/configs/igkboard-imx8mp_defconfig b/arch/arm64/configs/igkboard-imx8mp_defconfig
 new file mode 100644
-index 000000000..c0bf7a7f4
+index 000000000..b0f923742
 --- /dev/null
 +++ b/arch/arm64/configs/igkboard-imx8mp_defconfig
-@@ -0,0 +1,1127 @@
+@@ -0,0 +1,1103 @@
 +CONFIG_SYSVIPC=y
 +CONFIG_POSIX_MQUEUE=y
 +CONFIG_AUDIT=y
@@ -934,8 +1268,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
@@ -1506,27 +1839,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
@@ -1618,23 +1931,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
@@ -1804,3 +2114,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");

--
Gitblit v1.9.1