summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiddartha Mohanadoss <smohanad@codeaurora.org>2016-05-23 12:16:57 -0700
committerKyle Yan <kyan@codeaurora.org>2016-05-31 15:24:05 -0700
commit6cd193087140927f1c05d6aecd6dd1b555cf4a40 (patch)
treee3163bf8d2ec9d80b4465dc1021b3237eaed6bc8
parent2fcd240ed3cd80a5ee38ef599033e21e99772e2c (diff)
iio: adc: Add round robin ADC driver
Round robin ADC (RRADC) driver provides an interface to enable clients to read ADC values from the RRADC controller. The ADC values are updated in a round robin sequence among the enabled channels. Clients include reading voltage, current and temperature channels such as battery ID, battery thermistors, skin temperature, PMI die temperature, charger temperature, USB_IN and DCIN voltage and current. Change-Id: I927c0f6a7db654880d951729996a27310f6628cf Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/iio/adc/qcom-rradc.txt67
-rw-r--r--drivers/iio/adc/Kconfig15
-rw-r--r--drivers/iio/adc/Makefile1
-rw-r--r--drivers/iio/adc/qcom-rradc.c615
4 files changed, 698 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/iio/adc/qcom-rradc.txt b/Documentation/devicetree/bindings/iio/adc/qcom-rradc.txt
new file mode 100644
index 000000000000..8a64ccbd66c0
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/qcom-rradc.txt
@@ -0,0 +1,67 @@
+Qualcomm Technologies Inc., PMIC Round Robin ADC (RRADC)
+
+PMIC RRADC provides an interface to the clients to read
+the voltage, current and temperature for supported channels
+such as battery ID, battery thermistor, die temperature,
+charger temperature, USB_IN and DC_IN voltage and current.
+
+Main node properties:
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: Should contain "qcom,rradc".
+
+- reg:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: RRADC base address and length in the PMIC register map.
+
+- #address-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: Must be one. Child node 'channel' property should define ADC
+ channel number. For details about IIO bindings see:
+ Documentation/devicetree/bindings/iio/iio-bindings.txt
+
+- #size-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: Must be zero. For details about IIO bindings see:
+ Documentation/devicetree/bindings/iio/iio-bindings.txt
+
+- #io-channel-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: Must be one. For details about IIO bindings see:
+ Documentation/devicetree/bindings/iio/iio-bindings.txt
+
+Channel subnode properties:
+
+- channel:
+ Usage: required
+ Value type: <u32>
+ Definition: ADC channel number.
+ See drivers/iio/adc/qcom-rradc.c for channels within rradc_channel_id
+
+Example:
+
+ /* RRADC node */
+ pmic_rradc: rradc@4500 {
+ compatible = "qcom,rradc";
+ reg = <0x4500 0x100>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #io-channel-cells = <1>;
+
+ /* Channel node */
+ batt_id {
+ channel = <0>;
+ };
+ };
+
+ /* IIO client node */
+ charger {
+ io-channels = <&pmic_rradc 0>;
+ io-channel-names = "rradc_batt_id";
+ };
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 1e7aded53117..8a0e2c809fb2 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -303,6 +303,21 @@ config QCOM_SPMI_VADC
To compile this driver as a module, choose M here: the module will
be called qcom-spmi-vadc.
+config QCOM_RRADC
+ tristate "Qualcomm Technologies Inc. PMIC Round robin ADC"
+ depends on SPMI
+ select REGMAP_SPMI
+ help
+ This is the PMIC Round Robin ADC driver.
+
+ The driver supports multiple channels read used for telemetry
+ and supports clients to read batt_id, batt_therm, PMIC die
+ temperature, USB_IN and DC_IN voltage and current.
+ The RRADC is a 10-bit ADC.
+
+ To compile this driver as a module, choose M here: the module will
+ be called qcom-rradc.
+
config ROCKCHIP_SARADC
tristate "Rockchip SARADC driver"
depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST)
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 99b37a963a1e..59cac58d769e 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
obj-$(CONFIG_NAU7802) += nau7802.o
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
+obj-$(CONFIG_QCOM_RRADC) += qcom-rradc.o
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
diff --git a/drivers/iio/adc/qcom-rradc.c b/drivers/iio/adc/qcom-rradc.c
new file mode 100644
index 000000000000..dea0448c365c
--- /dev/null
+++ b/drivers/iio/adc/qcom-rradc.c
@@ -0,0 +1,615 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/iio/iio.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define FG_ADC_RR_EN_CTL 0x46
+#define FG_ADC_RR_SKIN_TEMP_LSB 0x50
+#define FG_ADC_RR_SKIN_TEMP_MSB 0x51
+#define FG_ADC_RR_RR_ADC_CTL 0x52
+
+#define FG_ADC_RR_FAKE_BATT_LOW_LSB 0x58
+#define FG_ADC_RR_FAKE_BATT_LOW_MSB 0x59
+#define FG_ADC_RR_FAKE_BATT_HIGH_LSB 0x5A
+#define FG_ADC_RR_FAKE_BATT_HIGH_MSB 0x5B
+
+#define FG_ADC_RR_BATT_ID_CTRL 0x60
+#define FG_ADC_RR_BATT_ID_TRIGGER 0x61
+#define FG_ADC_RR_BATT_ID_STS 0x62
+#define FG_ADC_RR_BATT_ID_CFG 0x63
+#define FG_ADC_RR_BATT_ID_5_LSB 0x66
+#define FG_ADC_RR_BATT_ID_5_MSB 0x67
+#define FG_ADC_RR_BATT_ID_15_LSB 0x68
+#define FG_ADC_RR_BATT_ID_15_MSB 0x69
+#define FG_ADC_RR_BATT_ID_150_LSB 0x6A
+#define FG_ADC_RR_BATT_ID_150_MSB 0x6B
+
+#define FG_ADC_RR_BATT_THERM_CTRL 0x70
+#define FG_ADC_RR_BATT_THERM_TRIGGER 0x71
+#define FG_ADC_RR_BATT_THERM_STS 0x72
+#define FG_ADC_RR_BATT_THERM_CFG 0x73
+#define FG_ADC_RR_BATT_THERM_LSB 0x74
+#define FG_ADC_RR_BATT_THERM_MSB 0x75
+#define FG_ADC_RR_BATT_THERM_FREQ 0x76
+
+#define FG_ADC_RR_AUX_THERM_CTRL 0x80
+#define FG_ADC_RR_AUX_THERM_TRIGGER 0x81
+#define FG_ADC_RR_AUX_THERM_STS 0x82
+#define FG_ADC_RR_AUX_THERM_CFG 0x83
+#define FG_ADC_RR_AUX_THERM_LSB 0x84
+#define FG_ADC_RR_AUX_THERM_MSB 0x85
+
+#define FG_ADC_RR_SKIN_HOT 0x86
+#define FG_ADC_RR_SKIN_TOO_HOT 0x87
+
+#define FG_ADC_RR_AUX_THERM_C1 0x88
+#define FG_ADC_RR_AUX_THERM_C2 0x89
+#define FG_ADC_RR_AUX_THERM_C3 0x8A
+#define FG_ADC_RR_AUX_THERM_HALF_RANGE 0x8B
+
+#define FG_ADC_RR_USB_IN_V_CTRL 0x90
+#define FG_ADC_RR_USB_IN_V_TRIGGER 0x91
+#define FG_ADC_RR_USB_IN_V_STS 0x92
+#define FG_ADC_RR_USB_IN_V_LSB 0x94
+#define FG_ADC_RR_USB_IN_V_MSB 0x95
+#define FG_ADC_RR_USB_IN_I_CTRL 0x98
+#define FG_ADC_RR_USB_IN_I_TRIGGER 0x99
+#define FG_ADC_RR_USB_IN_I_STS 0x9A
+#define FG_ADC_RR_USB_IN_I_LSB 0x9C
+#define FG_ADC_RR_USB_IN_I_MSB 0x9D
+
+#define FG_ADC_RR_DC_IN_V_CTRL 0xA0
+#define FG_ADC_RR_DC_IN_V_TRIGGER 0xA1
+#define FG_ADC_RR_DC_IN_V_STS 0xA2
+#define FG_ADC_RR_DC_IN_V_LSB 0xA4
+#define FG_ADC_RR_DC_IN_V_MSB 0xA5
+#define FG_ADC_RR_DC_IN_I_CTRL 0xA8
+#define FG_ADC_RR_DC_IN_I_TRIGGER 0xA9
+#define FG_ADC_RR_DC_IN_I_STS 0xAA
+#define FG_ADC_RR_DC_IN_I_LSB 0xAC
+#define FG_ADC_RR_DC_IN_I_MSB 0xAD
+
+#define FG_ADC_RR_PMI_DIE_TEMP_CTRL 0xB0
+#define FG_ADC_RR_PMI_DIE_TEMP_TRIGGER 0xB1
+#define FG_ADC_RR_PMI_DIE_TEMP_STS 0xB2
+#define FG_ADC_RR_PMI_DIE_TEMP_CFG 0xB3
+#define FG_ADC_RR_PMI_DIE_TEMP_LSB 0xB4
+#define FG_ADC_RR_PMI_DIE_TEMP_MSB 0xB5
+
+#define FG_ADC_RR_CHARGER_TEMP_CTRL 0xB8
+#define FG_ADC_RR_CHARGER_TEMP_TRIGGER 0xB9
+#define FG_ADC_RR_CHARGER_TEMP_STS 0xBA
+#define FG_ADC_RR_CHARGER_TEMP_CFG 0xBB
+#define FG_ADC_RR_CHARGER_TEMP_LSB 0xBC
+#define FG_ADC_RR_CHARGER_TEMP_MSB 0xBD
+#define FG_ADC_RR_CHARGER_HOT 0xBE
+#define FG_ADC_RR_CHARGER_TOO_HOT 0xBF
+
+#define FG_ADC_RR_GPIO_CTRL 0xC0
+#define FG_ADC_RR_GPIO_TRIGGER 0xC1
+#define FG_ADC_RR_GPIO_STS 0xC2
+#define FG_ADC_RR_GPIO_LSB 0xC4
+#define FG_ADC_RR_GPIO_MSB 0xC5
+
+#define FG_ADC_RR_ATEST_CTRL 0xC8
+#define FG_ADC_RR_ATEST_TRIGGER 0xC9
+#define FG_ADC_RR_ATEST_STS 0xCA
+#define FG_ADC_RR_ATEST_LSB 0xCC
+#define FG_ADC_RR_ATEST_MSB 0xCD
+#define FG_ADC_RR_SEC_ACCESS 0xD0
+
+#define FG_ADC_RR_PERPH_RESET_CTL2 0xD9
+#define FG_ADC_RR_PERPH_RESET_CTL3 0xDA
+#define FG_ADC_RR_PERPH_RESET_CTL4 0xDB
+#define FG_ADC_RR_INT_TEST1 0xE0
+#define FG_ADC_RR_INT_TEST_VAL 0xE1
+
+#define FG_ADC_RR_TM_TRIGGER_CTRLS 0xE2
+#define FG_ADC_RR_TM_ADC_CTRLS 0xE3
+#define FG_ADC_RR_TM_CNL_CTRL 0xE4
+#define FG_ADC_RR_TM_BATT_ID_CTRL 0xE5
+#define FG_ADC_RR_TM_THERM_CTRL 0xE6
+#define FG_ADC_RR_TM_CONV_STS 0xE7
+#define FG_ADC_RR_TM_ADC_READ_LSB 0xE8
+#define FG_ADC_RR_TM_ADC_READ_MSB 0xE9
+#define FG_ADC_RR_TM_ATEST_MUX_1 0xEA
+#define FG_ADC_RR_TM_ATEST_MUX_2 0xEB
+#define FG_ADC_RR_TM_REFERENCES 0xED
+#define FG_ADC_RR_TM_MISC_CTL 0xEE
+#define FG_ADC_RR_TM_RR_CTRL 0xEF
+
+#define FG_ADC_RR_BATT_ID_5_MA 5
+#define FG_ADC_RR_BATT_ID_15_MA 15
+#define FG_ADC_RR_BATT_ID_150_MA 150
+#define FG_ADC_RR_BATT_ID_RANGE 820
+
+#define FG_ADC_BITS 10
+#define FG_MAX_ADC_READINGS (1 << FG_ADC_BITS)
+#define FG_ADC_RR_FS_VOLTAGE_MV 2500
+
+/* BATT_THERM 0.25K/LSB */
+#define FG_ADC_RR_BATT_THERM_LSB_K 4
+
+#define FG_ADC_RR_TEMP_FS_VOLTAGE_NUM 5000000
+#define FG_ADC_RR_TEMP_FS_VOLTAGE_DEN 3
+#define FG_ADC_RR_DIE_TEMP_OFFSET 600000
+#define FG_ADC_RR_DIE_TEMP_SLOPE 2000
+#define FG_ADC_RR_DIE_TEMP_OFFSET_DEGC 25
+
+#define FG_ADC_RR_CHG_TEMP_OFFSET 1288000
+#define FG_ADC_RR_CHG_TEMP_SLOPE 4000
+#define FG_ADC_RR_CHG_TEMP_OFFSET_DEGC 27
+
+#define FG_ADC_RR_VOLT_INPUT_FACTOR 8
+#define FG_ADC_RR_CURR_INPUT_FACTOR 2
+#define FG_ADC_SCALE_MILLI_FACTOR 1000
+#define FG_ADC_KELVINMIL_CELSIUSMIL 273150
+
+#define FG_ADC_RR_GPIO_FS_RANGE 5000
+
+/*
+ * The channel number is not a physical index in hardware,
+ * rather it's a list of supported channels and an index to
+ * select the respective channel properties such as scaling
+ * the result. Add any new additional channels supported by
+ * the RR ADC before RR_ADC_MAX.
+ */
+enum rradc_channel_id {
+ RR_ADC_BATT_ID_5 = 0,
+ RR_ADC_BATT_ID_15,
+ RR_ADC_BATT_ID_150,
+ RR_ADC_BATT_ID,
+ RR_ADC_BATT_THERM,
+ RR_ADC_SKIN_TEMP,
+ RR_ADC_USBIN_V,
+ RR_ADC_USBIN_I,
+ RR_ADC_DCIN_V,
+ RR_ADC_DCIN_I,
+ RR_ADC_DIE_TEMP,
+ RR_ADC_CHG_TEMP,
+ RR_ADC_GPIO,
+ RR_ADC_ATEST,
+ RR_ADC_TM_ADC,
+ RR_ADC_MAX
+};
+
+struct rradc_chip {
+ struct device *dev;
+ struct mutex lock;
+ struct regmap *regmap;
+ u16 base;
+ struct iio_chan_spec *iio_chans;
+ unsigned int nchannels;
+ struct rradc_chan_prop *chan_props;
+};
+
+struct rradc_channels {
+ const char *datasheet_name;
+ enum iio_chan_type type;
+ long info_mask;
+ u8 lsb;
+ u8 msb;
+ int (*scale)(struct rradc_chip *chip, struct rradc_chan_prop *prop,
+ u16 adc_code, int *result);
+};
+
+struct rradc_chan_prop {
+ enum rradc_channel_id channel;
+ int (*scale)(struct rradc_chip *chip, struct rradc_chan_prop *prop,
+ u16 adc_code, int *result);
+};
+
+static int rradc_read(struct rradc_chip *rr_adc, u16 offset, u8 *data, int len)
+{
+ int rc = 0;
+
+ rc = regmap_bulk_read(rr_adc->regmap, rr_adc->base + offset, data, len);
+ if (rc < 0)
+ pr_err("rr adc read reg %d failed with %d\n", offset, rc);
+
+ return rc;
+}
+
+static int rradc_post_process_batt_id(struct rradc_chip *chip,
+ struct rradc_chan_prop *prop, u16 adc_code,
+ int *result_mohms)
+{
+ uint32_t current_value;
+ int64_t r_id;
+
+ switch (prop->channel) {
+ case RR_ADC_BATT_ID_5:
+ current_value = FG_ADC_RR_BATT_ID_5_MA;
+ break;
+ case RR_ADC_BATT_ID_15:
+ current_value = FG_ADC_RR_BATT_ID_15_MA;
+ break;
+ case RR_ADC_BATT_ID_150:
+ current_value = FG_ADC_RR_BATT_ID_150_MA;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ r_id = ((int64_t)adc_code * FG_ADC_RR_FS_VOLTAGE_MV);
+ r_id = div64_s64(r_id, (FG_MAX_ADC_READINGS * current_value));
+ *result_mohms = (r_id * FG_ADC_SCALE_MILLI_FACTOR);
+
+ return 0;
+}
+
+static int rradc_post_process_therm(struct rradc_chip *chip,
+ struct rradc_chan_prop *prop, u16 adc_code,
+ int *result_millidegc)
+{
+ int64_t temp;
+
+ /* K = code/4 */
+ temp = div64_s64(adc_code, FG_ADC_RR_BATT_THERM_LSB_K);
+ temp *= FG_ADC_SCALE_MILLI_FACTOR;
+ *result_millidegc = temp - FG_ADC_KELVINMIL_CELSIUSMIL;
+
+ return 0;
+}
+
+static int rradc_post_process_volt(struct rradc_chip *chip,
+ struct rradc_chan_prop *prop, u16 adc_code,
+ int *result_mv)
+{
+ int64_t mv = 0;
+
+ /* 8x input attenuation; 2.5V ADC full scale */
+ mv = ((int64_t)adc_code * FG_ADC_RR_VOLT_INPUT_FACTOR);
+ mv *= FG_ADC_RR_FS_VOLTAGE_MV;
+ mv = div64_s64(mv, FG_MAX_ADC_READINGS);
+ *result_mv = mv;
+
+ return 0;
+}
+
+static int rradc_post_process_curr(struct rradc_chip *chip,
+ struct rradc_chan_prop *prop, u16 adc_code,
+ int *result_ma)
+{
+ int64_t ma = 0;
+
+ /* 0.5 V/A; 2.5V ADC full scale */
+ ma = ((int64_t)adc_code * FG_ADC_RR_CURR_INPUT_FACTOR);
+ ma *= FG_ADC_RR_FS_VOLTAGE_MV;
+ ma = div64_s64(ma, FG_MAX_ADC_READINGS);
+ *result_ma = ma;
+
+ return 0;
+}
+
+static int rradc_post_process_die_temp(struct rradc_chip *chip,
+ struct rradc_chan_prop *prop, u16 adc_code,
+ int *result_millidegc)
+{
+ int64_t temp = 0;
+
+ temp = ((int64_t)adc_code * FG_ADC_RR_TEMP_FS_VOLTAGE_NUM);
+ temp = div64_s64(temp, (FG_ADC_RR_TEMP_FS_VOLTAGE_DEN *
+ FG_MAX_ADC_READINGS));
+ temp -= FG_ADC_RR_DIE_TEMP_OFFSET;
+ temp = div64_s64(temp, FG_ADC_RR_DIE_TEMP_SLOPE);
+ temp += FG_ADC_RR_DIE_TEMP_OFFSET_DEGC;
+ *result_millidegc = (temp * FG_ADC_SCALE_MILLI_FACTOR);
+
+ return 0;
+}
+
+static int rradc_post_process_chg_temp(struct rradc_chip *chip,
+ struct rradc_chan_prop *prop, u16 adc_code,
+ int *result_millidegc)
+{
+ int64_t temp = 0;
+
+ temp = ((int64_t) adc_code * FG_ADC_RR_TEMP_FS_VOLTAGE_NUM);
+ temp = div64_s64(temp, (FG_ADC_RR_TEMP_FS_VOLTAGE_DEN *
+ FG_MAX_ADC_READINGS));
+ temp = FG_ADC_RR_CHG_TEMP_OFFSET - temp;
+ temp = div64_s64(temp, FG_ADC_RR_CHG_TEMP_SLOPE);
+ temp = temp + FG_ADC_RR_CHG_TEMP_OFFSET_DEGC;
+ *result_millidegc = (temp * FG_ADC_SCALE_MILLI_FACTOR);
+
+ return 0;
+}
+
+static int rradc_post_process_gpio(struct rradc_chip *chip,
+ struct rradc_chan_prop *prop, u16 adc_code,
+ int *result_mv)
+{
+ int64_t mv = 0;
+
+ /* 5V ADC full scale, 10 bit */
+ mv = ((int64_t)adc_code * FG_ADC_RR_GPIO_FS_RANGE);
+ mv = div64_s64(mv, FG_MAX_ADC_READINGS);
+ *result_mv = mv;
+
+ return 0;
+}
+
+#define RR_ADC_CHAN(_dname, _type, _mask, _scale, _lsb, _msb) \
+ { \
+ .datasheet_name = __stringify(_dname), \
+ .type = _type, \
+ .info_mask = _mask, \
+ .scale = _scale, \
+ .lsb = _lsb, \
+ .msb = _msb, \
+ }, \
+
+#define RR_ADC_CHAN_TEMP(_dname, _scale, _lsb, _msb) \
+ RR_ADC_CHAN(_dname, IIO_TEMP, \
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), \
+ _scale, _lsb, _msb) \
+
+#define RR_ADC_CHAN_VOLT(_dname, _scale, _lsb, _msb) \
+ RR_ADC_CHAN(_dname, IIO_VOLTAGE, \
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
+ _scale, _lsb, _msb) \
+
+#define RR_ADC_CHAN_CURRENT(_dname, _scale, _lsb, _msb) \
+ RR_ADC_CHAN(_dname, IIO_CURRENT, \
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
+ _scale, _lsb, _msb) \
+
+#define RR_ADC_CHAN_RESISTANCE(_dname, _scale, _lsb, _msb) \
+ RR_ADC_CHAN(_dname, IIO_RESISTANCE, \
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
+ _scale, _lsb, _msb) \
+
+static const struct rradc_channels rradc_chans[] = {
+ RR_ADC_CHAN_RESISTANCE("batt_id_5", rradc_post_process_batt_id,
+ FG_ADC_RR_BATT_ID_5_LSB, FG_ADC_RR_BATT_ID_5_MSB)
+ RR_ADC_CHAN_RESISTANCE("batt_id_15", rradc_post_process_batt_id,
+ FG_ADC_RR_BATT_ID_15_LSB, FG_ADC_RR_BATT_ID_15_MSB)
+ RR_ADC_CHAN_RESISTANCE("batt_id_150", rradc_post_process_batt_id,
+ FG_ADC_RR_BATT_ID_150_LSB, FG_ADC_RR_BATT_ID_150_MSB)
+ RR_ADC_CHAN_RESISTANCE("batt_id", rradc_post_process_batt_id,
+ FG_ADC_RR_BATT_ID_5_LSB, FG_ADC_RR_BATT_ID_5_MSB)
+ RR_ADC_CHAN_TEMP("batt_therm", &rradc_post_process_therm,
+ FG_ADC_RR_BATT_THERM_LSB, FG_ADC_RR_BATT_THERM_MSB)
+ RR_ADC_CHAN_TEMP("skin_temp", &rradc_post_process_therm,
+ FG_ADC_RR_SKIN_TEMP_LSB, FG_ADC_RR_SKIN_TEMP_MSB)
+ RR_ADC_CHAN_CURRENT("usbin_i", &rradc_post_process_curr,
+ FG_ADC_RR_USB_IN_V_LSB, FG_ADC_RR_USB_IN_V_MSB)
+ RR_ADC_CHAN_VOLT("usbin_v", &rradc_post_process_volt,
+ FG_ADC_RR_USB_IN_I_LSB, FG_ADC_RR_USB_IN_I_MSB)
+ RR_ADC_CHAN_CURRENT("dcin_i", &rradc_post_process_curr,
+ FG_ADC_RR_DC_IN_V_LSB, FG_ADC_RR_DC_IN_V_MSB)
+ RR_ADC_CHAN_VOLT("dcin_v", &rradc_post_process_volt,
+ FG_ADC_RR_DC_IN_I_LSB, FG_ADC_RR_DC_IN_I_MSB)
+ RR_ADC_CHAN_TEMP("die_temp", &rradc_post_process_die_temp,
+ FG_ADC_RR_PMI_DIE_TEMP_LSB, FG_ADC_RR_PMI_DIE_TEMP_MSB)
+ RR_ADC_CHAN_TEMP("chg_temp", &rradc_post_process_chg_temp,
+ FG_ADC_RR_CHARGER_TEMP_LSB, FG_ADC_RR_CHARGER_TEMP_MSB)
+ RR_ADC_CHAN_VOLT("gpio", &rradc_post_process_gpio,
+ FG_ADC_RR_GPIO_LSB, FG_ADC_RR_GPIO_MSB)
+};
+
+static int rradc_do_conversion(struct rradc_chip *chip,
+ struct rradc_chan_prop *prop, u16 *data)
+{
+ int rc = 0, bytes_to_read = 0;
+ u8 buf[6];
+ u16 offset = 0, batt_id_5 = 0, batt_id_15 = 0, batt_id_150 = 0;
+
+ mutex_lock(&chip->lock);
+
+ offset = rradc_chans[prop->channel].lsb;
+ if (prop->channel == RR_ADC_BATT_ID)
+ bytes_to_read = 6;
+ else
+ bytes_to_read = 2;
+
+ rc = rradc_read(chip, offset, buf, bytes_to_read);
+ if (rc) {
+ pr_err("read data failed\n");
+ goto fail;
+ }
+
+ if (prop->channel == RR_ADC_BATT_ID) {
+ batt_id_150 = (buf[4] << 8) | buf[5];
+ batt_id_15 = (buf[2] << 8) | buf[3];
+ batt_id_5 = (buf[0] << 8) | buf[1];
+ if (batt_id_150 <= FG_ADC_RR_BATT_ID_RANGE) {
+ pr_debug("Batt_id_150 is chosen\n");
+ *data = batt_id_150;
+ } else if (batt_id_15 <= FG_ADC_RR_BATT_ID_RANGE) {
+ pr_debug("Batt_id_15 is chosen\n");
+ *data = batt_id_15;
+ } else {
+ pr_debug("Batt_id_5 is chosen\n");
+ *data = batt_id_5;
+ }
+ } else {
+ *data = (buf[1] << 8) | buf[0];
+ }
+fail:
+ mutex_unlock(&chip->lock);
+
+ return rc;
+}
+
+static int rradc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val, int *val2,
+ long mask)
+{
+ struct rradc_chip *chip = iio_priv(indio_dev);
+ struct rradc_chan_prop *prop;
+ u16 adc_code;
+ int rc = 0;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ prop = &chip->chan_props[chan->address];
+ rc = rradc_do_conversion(chip, prop, &adc_code);
+ if (rc)
+ break;
+
+ prop->scale(chip, prop, adc_code, val);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_RAW:
+ prop = &chip->chan_props[chan->address];
+ rc = rradc_do_conversion(chip, prop, &adc_code);
+ if (rc)
+ break;
+
+ *val = (int) adc_code;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ *val2 = 1000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static const struct iio_info rradc_info = {
+ .read_raw = &rradc_read_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static int rradc_get_dt_data(struct rradc_chip *chip, struct device_node *node)
+{
+ const struct rradc_channels *rradc_chan;
+ struct iio_chan_spec *iio_chan;
+ struct device_node *child;
+ unsigned int index = 0, chan, base;
+ int rc = 0;
+ struct rradc_chan_prop prop;
+
+ chip->nchannels = of_get_available_child_count(node);
+ if (!chip->nchannels || (chip->nchannels >= RR_ADC_MAX))
+ return -EINVAL;
+
+ chip->iio_chans = devm_kcalloc(chip->dev, chip->nchannels,
+ sizeof(*chip->iio_chans), GFP_KERNEL);
+ if (!chip->iio_chans)
+ return -ENOMEM;
+
+ chip->chan_props = devm_kcalloc(chip->dev, chip->nchannels,
+ sizeof(*chip->chan_props), GFP_KERNEL);
+ if (!chip->chan_props)
+ return -ENOMEM;
+
+ /* Get the peripheral address */
+ rc = of_property_read_u32(node, "reg", &base);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ node->name, rc);
+ return rc;
+ }
+
+ chip->base = base;
+ iio_chan = chip->iio_chans;
+
+ for_each_available_child_of_node(node, child) {
+ rc = of_property_read_u32(child, "channel", &chan);
+ if (rc) {
+ dev_err(chip->dev, "invalid channel number %d\n", chan);
+ return rc;
+ }
+
+ if (chan > RR_ADC_MAX || chan < RR_ADC_BATT_ID_5) {
+ dev_err(chip->dev, "invalid channel number %d\n", chan);
+ return -EINVAL;
+ }
+
+ prop.channel = chan;
+ prop.scale = rradc_chans[chan].scale;
+ chip->chan_props[index] = prop;
+
+ rradc_chan = &rradc_chans[chan];
+
+ iio_chan->channel = prop.channel;
+ iio_chan->datasheet_name = rradc_chan->datasheet_name;
+ iio_chan->info_mask_separate = rradc_chan->info_mask;
+ iio_chan->type = rradc_chan->type;
+ iio_chan->indexed = 1;
+ iio_chan->address = index++;
+ iio_chan++;
+ }
+
+ return 0;
+}
+
+static int rradc_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct iio_dev *indio_dev;
+ struct rradc_chip *chip;
+ int rc = 0;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ chip = iio_priv(indio_dev);
+ chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ chip->dev = dev;
+ mutex_init(&chip->lock);
+
+ rc = rradc_get_dt_data(chip, node);
+ if (rc)
+ return rc;
+
+ indio_dev->dev.parent = dev;
+ indio_dev->dev.of_node = node;
+ indio_dev->name = pdev->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &rradc_info;
+ indio_dev->channels = chip->iio_chans;
+ indio_dev->num_channels = chip->nchannels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id rradc_match_table[] = {
+ { .compatible = "qcom,rradc" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rradc_match_table);
+
+static struct platform_driver rradc_driver = {
+ .driver = {
+ .name = "qcom-rradc",
+ .of_match_table = rradc_match_table,
+ },
+ .probe = rradc_probe,
+};
+module_platform_driver(rradc_driver);
+
+MODULE_DESCRIPTION("QPNP PMIC RR ADC driver");
+MODULE_LICENSE("GPL v2");