From 35deaefe2a198508903c5657a0fa7cb2ad60f56d Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Wed, 17 Jul 2024 14:20:23 +0800
Subject: [PATCH] Patch:IGKBoard-IMX8MP: Add WM8960 audio codec support
---
kernel/patches/igkboard-imx8mp/linux-imx-lf-6.1.36-2.1.0.patch | 837 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 795 insertions(+), 42 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 aa84b67..733e728 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,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..ce38a5b4a
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/igkboard-imx8mp.dts
-@@ -0,0 +1,613 @@
+@@ -0,0 +1,782 @@
+// 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,104 @@
+ 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";
++};
++
++/*+------------------------+
++ | 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 +426,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>;
++ };
+};
+
+/*+------------------------+
@@ -470,6 +600,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 +637,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 +798,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 +965,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 +991,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 +1059,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 +1147,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 +1630,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 +1722,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 +1813,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 +1905,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