diff --git a/Documentation/devicetree/bindings/hwmon/qpnp-adc-voltage.txt b/Documentation/devicetree/bindings/hwmon/qpnp-adc-voltage.txt new file mode 100644 index 000000000000..2ba7341fa41d --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/qpnp-adc-voltage.txt @@ -0,0 +1,98 @@ +Qualcomm's QPNP PMIC Voltage ADC Arbiter + +QPNP PMIC Voltage ADC (VADC) provides interface to clients to read +Voltage. A 15 bit ADC is used for Voltage measurements. There are multiple +peripherals to the VADC and the scope of the driver is to provide interface +for the USR peripheral of the VADC. + +VADC node + +Required properties: +- compatible : should be "qcom,qpnp-vadc" for Voltage ADC driver. +- reg : offset and length of the PMIC Aribter register map. +- interrupts : The USR bank peripheral VADC interrupt. +- qcom,adc-bit-resolution : Bit resolution of the ADC. +- qcom,adc-vdd-reference : Voltage reference used by the ADC. + +Channel nodes +NOTE: Atleast one Channel node is required. + +Required properties: +- label : Channel name used for sysfs entry. +- qcom,channel-num : Channel number associated to the AMUX input. +- qcom,decimation : Sampling rate to use for the individual channel measurement. + Select from following unsigned int. + 0 : 512 + 1 : 1K + 2 : 2K + 3 : 4K +- qcom,pre-div-channel-scaling : Pre-div used for the channel before the signal + is being measured. +- qcom,calibration-type : Reference voltage to use for channel calibration. + Channel calibration is dependendent on the channel. + Certain channels like XO_THERM, BATT_THERM use ratiometric + calibration. Most other channels fall under absolute calibration. + Select from the following strings. + "absolute" : Uses the 625mv and 1.25V reference channels. + "ratiometric" : Uses the reference Voltage/GND for calibration. +- qcom,scale-function : Scaling function used to convert raw ADC code to units specific to + a given channel. + Select from the following unsigned int. + 0 : Default scaling to convert raw adc code to voltage. + 1 : Conversion to temperature based on btm parameters. + 2 : Returns result in milli degree's Centigrade. + 3 : Returns current across 0.1 ohm resistor. + 4 : Returns XO thermistor voltage in degree's Centigrade. +- qcom,hw-settle-time : Settling period for the channel before ADC read. + Select from the following unsigned int. + 0 : 0us + 1 : 100us + 2 : 200us + 3 : 300us + 4 : 400us + 5 : 500us + 6 : 600us + 7 : 700us + 8 : 800us + 9 : 900us + 0xa : 1ms + 0xb : 2ms + 0xc : 4ms + 0xd : 6ms + 0xe : 8ms + 0xf : 10ms +- qcom,fast-avg-setup : Average number of samples to be used for measurement. Fast averaging + provides the option to obtain a single measurement from the ADC that + is an average of multiple samples. The value selected is 2^(value) + Select from the following unsigned int. + 0 : 1 + 1 : 2 + 2 : 4 + 3 : 8 + 4 : 16 + 5 : 32 + 6 : 64 + 7 : 128 + 8 : 256 + +Example: + /* Main Node */ + qcom,vadc@3100 { + compatible = "qcom,qpnp-vadc"; + reg = <0x3100 0x100>; + interrupts = <0x0 0x31 0x0>; + qcom,adc-bit-resolution = <15>; + qcom,adc-vdd-reference = <1800>; + + /* Channel Node */ + chan@0 { + label = "usb_in"; + qcom,channel-num = <0>; + qcom,decimation = <0>; + qcom,pre-div-channel-scaling = <20>; + qcom,calibration-type = "absolute"; + qcom,scale-function = <0>; + qcom,hw-settle-time = <0>; + qcom,fast-avg-setup = <0>; + }; + }; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5e39dbd62d46..c5991bd7f289 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -986,6 +986,16 @@ config SENSORS_EPM_ADC Provides interface for measuring the current on specific power rails through the channels on ADC1158 ADC +config SENSORS_QPNP_ADC_VOLTAGE + tristate "Support for Qualcomm QPNP Voltage ADC" + depends on SPMI + help + This is the VADC arbiter driver for Qualcomm QPNP ADC Chip. + + The driver supports reading the HKADC, XOADC through the ADC AMUX arbiter. + The VADC includes support for the conversion sequencer. The driver supports + reading the ADC through the AMUX channels for external pull-ups simultaneously. + config SENSORS_PC87360 tristate "National Semiconductor PC87360 family" depends on !PPC diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index cd4ce5b1d984..8170b109b771 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -144,6 +144,7 @@ obj-$(CONFIG_SENSORS_WPCE775X) += wpce775x.o obj-$(CONFIG_SENSORS_MSM_ADC) += msm_adc.o m_adcproc.o obj-$(CONFIG_SENSORS_PM8XXX_ADC) += pm8xxx-adc.o pm8xxx-adc-scale.o obj-$(CONFIG_SENSORS_EPM_ADC) += epm_adc.o +obj-$(CONFIG_SENSORS_QPNP_ADC_VOLTAGE) += qpnp-adc-voltage.o qpnp-adc-common.o obj-$(CONFIG_PMBUS) += pmbus/ diff --git a/drivers/hwmon/qpnp-adc-common.c b/drivers/hwmon/qpnp-adc-common.c new file mode 100644 index 000000000000..1b2ad3d4cd2c --- /dev/null +++ b/drivers/hwmon/qpnp-adc-common.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2012, 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Min ADC code represets 0V */ +#define QPNP_VADC_MIN_ADC_CODE 0x6000 +/* Max ADC code represents full-scale range of 1.8V */ +#define QPNP_VADC_MAX_ADC_CODE 0xA800 + +int32_t qpnp_adc_scale_default(int32_t adc_code, + const struct qpnp_adc_properties *adc_properties, + const struct qpnp_vadc_chan_properties *chan_properties, + struct qpnp_vadc_result *adc_chan_result) +{ + bool negative_rawfromoffset = 0, negative_offset = 0; + int64_t scale_voltage = 0; + + if (!chan_properties || !chan_properties->offset_gain_numerator || + !chan_properties->offset_gain_denominator || !adc_properties + || !adc_chan_result) + return -EINVAL; + + scale_voltage = (adc_code - + chan_properties->adc_graph[CALIB_ABSOLUTE].adc_gnd) + * chan_properties->adc_graph[CALIB_ABSOLUTE].dx; + if (scale_voltage < 0) { + negative_offset = 1; + scale_voltage = -scale_voltage; + } + do_div(scale_voltage, + chan_properties->adc_graph[CALIB_ABSOLUTE].dy); + if (negative_offset) + scale_voltage = -scale_voltage; + scale_voltage += chan_properties->adc_graph[CALIB_ABSOLUTE].dx; + + if (scale_voltage < 0) { + if (adc_properties->bipolar) { + scale_voltage = -scale_voltage; + negative_rawfromoffset = 1; + } else { + scale_voltage = 0; + } + } + + adc_chan_result->measurement = scale_voltage * + chan_properties->offset_gain_denominator; + + /* do_div only perform positive integer division! */ + do_div(adc_chan_result->measurement, + chan_properties->offset_gain_numerator); + + if (negative_rawfromoffset) + adc_chan_result->measurement = -adc_chan_result->measurement; + + /* + * Note: adc_chan_result->measurement is in the unit of + * adc_properties.adc_reference. For generic channel processing, + * channel measurement is a scale/ratio relative to the adc + * reference input + */ + adc_chan_result->physical = adc_chan_result->measurement; + + return 0; +} +EXPORT_SYMBOL_GPL(qpnp_adc_scale_default); + +int32_t qpnp_vadc_check_result(int32_t *data) +{ + if (*data < QPNP_VADC_MIN_ADC_CODE) + *data = QPNP_VADC_MIN_ADC_CODE; + else if (*data > QPNP_VADC_MAX_ADC_CODE) + *data = QPNP_VADC_MAX_ADC_CODE; + + return 0; +} +EXPORT_SYMBOL_GPL(qpnp_vadc_check_result); + +int32_t qpnp_adc_get_devicetree_data(struct spmi_device *spmi, + struct qpnp_adc_drv *adc_qpnp) +{ + struct device_node *node = spmi->dev.of_node; + struct resource *res; + struct device_node *child; + struct qpnp_vadc_amux *adc_channel_list; + struct qpnp_adc_properties *adc_prop; + struct qpnp_vadc_amux_properties *amux_prop; + int count_adc_channel_list = 0, decimation, rc = 0; + + if (!node) + return -EINVAL; + + for_each_child_of_node(node, child) + count_adc_channel_list++; + + if (!count_adc_channel_list) { + pr_err("No channel listing\n"); + return -EINVAL; + } + + adc_qpnp->spmi = spmi; + + adc_prop = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_adc_properties), + GFP_KERNEL); + if (!adc_prop) { + dev_err(&spmi->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + adc_channel_list = devm_kzalloc(&spmi->dev, + (sizeof(struct qpnp_vadc_amux) * count_adc_channel_list), + GFP_KERNEL); + if (!adc_channel_list) { + dev_err(&spmi->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + amux_prop = devm_kzalloc(&spmi->dev, + sizeof(struct qpnp_vadc_amux_properties) + + sizeof(struct qpnp_vadc_chan_properties), GFP_KERNEL); + if (!amux_prop) { + dev_err(&spmi->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + for_each_child_of_node(node, child) { + int channel_num, scaling, post_scaling, hw_settle_time; + int fast_avg_setup, calib_type, i = 0, rc; + const char *calibration_param, *channel_name; + + channel_name = of_get_property(child, + "label", NULL) ? : child->name; + if (!channel_name) { + pr_err("Invalid channel name\n"); + return -EINVAL; + } + + rc = of_property_read_u32(child, "qcom,channel-num", + &channel_num); + if (rc) { + pr_err("Invalid channel num\n"); + return -EINVAL; + } + rc = of_property_read_u32(child, "qcom,decimation", + &decimation); + if (rc) { + pr_err("Invalid channel decimation property\n"); + return -EINVAL; + } + rc = of_property_read_u32(child, + "qcom,pre-div-channel-scaling", &scaling); + if (rc) { + pr_err("Invalid channel scaling property\n"); + return -EINVAL; + } + rc = of_property_read_u32(child, + "qcom,scale-function", &post_scaling); + if (rc) { + pr_err("Invalid channel post scaling property\n"); + return -EINVAL; + } + rc = of_property_read_u32(child, + "qcom,hw-settle-time", &hw_settle_time); + if (rc) { + pr_err("Invalid channel hw settle time property\n"); + return -EINVAL; + } + rc = of_property_read_u32(child, + "qcom,fast-avg-setup", &fast_avg_setup); + if (rc) { + pr_err("Invalid channel fast average setup\n"); + return -EINVAL; + } + calibration_param = of_get_property(child, + "qcom,calibration-type", NULL); + if (!strncmp(calibration_param, "absolute", 8)) + calib_type = CALIB_ABSOLUTE; + else if (!strncmp(calibration_param, "historical", 9)) + calib_type = CALIB_RATIOMETRIC; + else { + pr_err("%s: Invalid calibration property\n", __func__); + return -EINVAL; + } + /* Individual channel properties */ + adc_channel_list[i].name = (char *)channel_name; + adc_channel_list[i].channel_num = channel_num; + adc_channel_list[i].chan_path_prescaling = scaling; + adc_channel_list[i].adc_decimation = decimation; + adc_channel_list[i].adc_scale_fn = post_scaling; + adc_channel_list[i].hw_settle_time = hw_settle_time; + adc_channel_list[i].fast_avg_setup = fast_avg_setup; + i++; + } + adc_qpnp->adc_channels = adc_channel_list; + adc_qpnp->amux_prop = amux_prop; + + /* Get the ADC VDD reference voltage and ADC bit resolution */ + rc = of_property_read_u32(node, "qcom,adc-vdd-reference", + &adc_prop->adc_vdd_reference); + if (rc) { + pr_err("Invalid adc vdd reference property\n"); + return -EINVAL; + } + rc = of_property_read_u32(node, "qcom,adc-bit-resolution", + &adc_prop->bitresolution); + if (rc) { + pr_err("Invalid adc bit resolution property\n"); + return -EINVAL; + } + adc_qpnp->adc_prop = adc_prop; + + /* Get the peripheral address */ + res = spmi_get_resource(spmi, 0, IORESOURCE_MEM, 0); + if (!res) { + pr_err("No base address definition\n"); + return -EINVAL; + } + + adc_qpnp->slave = spmi->sid; + adc_qpnp->offset = res->start; + + /* Register the ADC peripheral interrupt */ + adc_qpnp->adc_irq = spmi_get_irq(spmi, 0, 0); + if (adc_qpnp->adc_irq < 0) { + pr_err("Invalid irq\n"); + return -ENXIO; + } + + mutex_init(&adc_qpnp->adc_lock); + + return 0; +} +EXPORT_SYMBOL(qpnp_adc_get_devicetree_data); diff --git a/drivers/hwmon/qpnp-adc-voltage.c b/drivers/hwmon/qpnp-adc-voltage.c new file mode 100644 index 000000000000..80716dd2b67a --- /dev/null +++ b/drivers/hwmon/qpnp-adc-voltage.c @@ -0,0 +1,784 @@ +/* Copyright (c) 2012, 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* QPNP VADC register definition */ +#define QPNP_VADC_STATUS1 0x8 +#define QPNP_VADC_STATUS1_OP_MODE 4 +#define QPNP_VADC_STATUS1_MEAS_INTERVAL_EN_STS BIT(2) +#define QPNP_VADC_STATUS1_REQ_STS BIT(1) +#define QPNP_VADC_STATUS1_EOC BIT(0) +#define QPNP_VADC_STATUS2 0x9 +#define QPNP_VADC_STATUS2_CONV_SEQ_STATE 6 +#define QPNP_VADC_STATUS2_FIFO_NOT_EMPTY_FLAG BIT(1) +#define QPNP_VADC_STATUS2_CONV_SEQ_TIMEOUT_STS BIT(0) +#define QPNP_VADC_STATUS2_CONV_SEQ_STATE_SHIFT 4 +#define QPNP_VADC_CONV_TIMEOUT_ERR 2 + +#define QPNP_VADC_INT_SET_TYPE 0x11 +#define QPNP_VADC_INT_POLARITY_HIGH 0x12 +#define QPNP_VADC_INT_POLARITY_LOW 0x13 +#define QPNP_VADC_INT_LATCHED_CLR 0x14 +#define QPNP_VADC_INT_EN_SET 0x15 +#define QPNP_VADC_INT_CLR 0x16 +#define QPNP_VADC_INT_LOW_THR_BIT BIT(4) +#define QPNP_VADC_INT_HIGH_THR_BIT BIT(3) +#define QPNP_VADC_INT_CONV_SEQ_TIMEOUT_BIT BIT(2) +#define QPNP_VADC_INT_FIFO_NOT_EMPTY_BIT BIT(1) +#define QPNP_VADC_INT_EOC_BIT BIT(0) +#define QPNP_VADC_INT_CLR_MASK 0x1f +#define QPNP_VADC_MODE_CTL 0x40 +#define QPNP_VADC_OP_MODE_SHIFT 4 +#define QPNP_VADC_VREF_XO_THM_FORCE BIT(2) +#define QPNP_VADC_AMUX_TRIM_EN BIT(1) +#define QPNP_VADC_ADC_TRIM_EN BIT(0) +#define QPNP_VADC_EN_CTL1 0x46 +#define QPNP_VADC_ADC_EN BIT(7) +#define QPNP_VADC_ADC_CH_SEL_CTL 0x48 +#define QPNP_VADC_ADC_DIG_PARAM 0x50 +#define QPNP_VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT 3 +#define QPNP_VADC_HW_SETTLE_DELAY 0x51 +#define QPNP_VADC_CONV_REQ 0x52 +#define QPNP_VADC_CONV_REQ_SET BIT(7) +#define QPNP_VADC_CONV_SEQ_CTL 0x54 +#define QPNP_VADC_CONV_SEQ_HOLDOFF_SHIFT 4 +#define QPNP_VADC_CONV_SEQ_TRIG_CTL 0x55 +#define QPNP_VADC_CONV_SEQ_FALLING_EDGE 0x0 +#define QPNP_VADC_CONV_SEQ_RISING_EDGE 0x1 +#define QPNP_VADC_CONV_SEQ_EDGE_SHIFT 7 +#define QPNP_VADC_FAST_AVG_CTL 0x5a + +#define QPNP_VADC_M0_LOW_THR_LSB 0x5c +#define QPNP_VADC_M0_LOW_THR_MSB 0x5d +#define QPNP_VADC_M0_HIGH_THR_LSB 0x5e +#define QPNP_VADC_M0_HIGH_THR_MSB 0x5f +#define QPNP_VADC_M1_LOW_THR_LSB 0x69 +#define QPNP_VADC_M1_LOW_THR_MSB 0x6a +#define QPNP_VADC_M1_HIGH_THR_LSB 0x6b +#define QPNP_VADC_M1_HIGH_THR_MSB 0x6c + +#define QPNP_VADC_DATA0 0x60 +#define QPNP_VADC_DATA1 0x61 +#define QPNP_VADC_CONV_TIMEOUT_ERR 2 +#define QPNP_VADC_CONV_TIME_MIN 2000 +#define QPNP_VADC_CONV_TIME_MAX 2100 + +#define QPNP_ADC_HWMON_NAME_LENGTH 16 + +struct qpnp_vadc_drv { + struct qpnp_adc_drv *adc; + struct dentry *dent; + struct device *vadc_hwmon; + bool vadc_init_calib; + struct sensor_device_attribute sens_attr[0]; +}; + +struct qpnp_vadc_drv *qpnp_vadc; + +static struct qpnp_vadc_scale_fn vadc_scale_fn[] = { + [SCALE_DEFAULT] = {qpnp_adc_scale_default}, +}; + +static int32_t qpnp_vadc_read_reg(int16_t reg, u8 *data) +{ + struct qpnp_vadc_drv *vadc = qpnp_vadc; + int rc; + + rc = spmi_ext_register_readl(vadc->adc->spmi->ctrl, vadc->adc->slave, + reg, data, 1); + if (rc < 0) { + pr_err("qpnp adc read reg %d failed with %d\n", reg, rc); + return rc; + } + + return 0; +} + +static int32_t qpnp_vadc_write_reg(int16_t reg, u8 data) +{ + struct qpnp_vadc_drv *vadc = qpnp_vadc; + int rc; + u8 *buf; + + buf = &data; + + rc = spmi_ext_register_writel(vadc->adc->spmi->ctrl, vadc->adc->slave, + reg, buf, 1); + if (rc < 0) { + pr_err("qpnp adc write reg %d failed with %d\n", reg, rc); + return rc; + } + + return 0; +} + +static int32_t qpnp_vadc_configure_interrupt(void) +{ + int rc = 0; + u8 data = 0; + + /* Configure interrupt as an Edge trigger */ + rc = qpnp_vadc_write_reg(QPNP_VADC_INT_SET_TYPE, + QPNP_VADC_INT_CLR_MASK); + if (rc < 0) { + pr_err("%s Interrupt configure failed\n", __func__); + return rc; + } + + /* Configure interrupt for rising edge trigger */ + rc = qpnp_vadc_write_reg(QPNP_VADC_INT_POLARITY_HIGH, + QPNP_VADC_INT_CLR_MASK); + if (rc < 0) { + pr_err("%s Rising edge trigger configure failed\n", __func__); + return rc; + } + + /* Disable low level interrupt triggering */ + data = QPNP_VADC_INT_CLR_MASK; + rc = qpnp_vadc_write_reg(QPNP_VADC_INT_POLARITY_LOW, + (~data & QPNP_VADC_INT_CLR_MASK)); + if (rc < 0) { + pr_err("%s Setting level low to disable failed\n", __func__); + return rc; + } + + return 0; +} + +static int32_t qpnp_vadc_enable(bool state) +{ + int rc = 0; + u8 data = 0; + + data = QPNP_VADC_ADC_EN; + if (state) { + rc = qpnp_vadc_write_reg(QPNP_VADC_EN_CTL1, + data); + if (rc < 0) { + pr_err("VADC enable failed\n"); + return rc; + } + } else { + rc = qpnp_vadc_write_reg(QPNP_VADC_EN_CTL1, + (~data & QPNP_VADC_ADC_EN)); + if (rc < 0) { + pr_err("VADC disable failed\n"); + return rc; + } + } + + return 0; +} + +int32_t qpnp_vadc_configure( + struct qpnp_vadc_amux_properties *chan_prop) +{ + u8 decimation = 0, conv_sequence = 0, conv_sequence_trig = 0; + int rc = 0; + + rc = qpnp_vadc_write_reg(QPNP_VADC_INT_EN_SET, + QPNP_VADC_INT_EOC_BIT); + if (rc < 0) { + pr_err("qpnp adc configure error for interrupt setup\n"); + return rc; + } + + rc = qpnp_vadc_write_reg(QPNP_VADC_MODE_CTL, chan_prop->mode_sel); + if (rc < 0) { + pr_err("qpnp adc configure error for mode selection\n"); + return rc; + } + + rc = qpnp_vadc_write_reg(QPNP_VADC_ADC_CH_SEL_CTL, + chan_prop->amux_channel); + if (rc < 0) { + pr_err("qpnp adc configure error for channel selection\n"); + return rc; + } + + decimation |= chan_prop->decimation << + QPNP_VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT; + rc = qpnp_vadc_write_reg(QPNP_VADC_ADC_DIG_PARAM, decimation); + if (rc < 0) { + pr_err("qpnp adc configure error for digital parameter setup\n"); + return rc; + } + + rc = qpnp_vadc_write_reg(QPNP_VADC_HW_SETTLE_DELAY, + chan_prop->hw_settle_time); + if (rc < 0) { + pr_err("qpnp adc configure error for hw settling time setup\n"); + return rc; + } + + if (chan_prop->mode_sel == (ADC_OP_NORMAL_MODE << + QPNP_VADC_OP_MODE_SHIFT)) { + rc = qpnp_vadc_write_reg(QPNP_VADC_FAST_AVG_CTL, + chan_prop->fast_avg_setup); + if (rc < 0) { + pr_err("qpnp adc fast averaging configure error\n"); + return rc; + } + } else if (chan_prop->mode_sel == (ADC_OP_CONVERSION_SEQUENCER << + QPNP_VADC_OP_MODE_SHIFT)) { + conv_sequence = ((ADC_SEQ_HOLD_100US << + QPNP_VADC_CONV_SEQ_HOLDOFF_SHIFT) | + ADC_CONV_SEQ_TIMEOUT_5MS); + rc = qpnp_vadc_write_reg(QPNP_VADC_CONV_SEQ_CTL, + conv_sequence); + if (rc < 0) { + pr_err("qpnp adc conversion sequence error\n"); + return rc; + } + + conv_sequence_trig = ((QPNP_VADC_CONV_SEQ_RISING_EDGE << + QPNP_VADC_CONV_SEQ_EDGE_SHIFT) | + chan_prop->trigger_channel); + rc = qpnp_vadc_write_reg(QPNP_VADC_CONV_SEQ_TRIG_CTL, + conv_sequence_trig); + if (rc < 0) { + pr_err("qpnp adc conversion trigger error\n"); + return rc; + } + } + + rc = qpnp_vadc_write_reg(QPNP_VADC_CONV_REQ, QPNP_VADC_CONV_REQ_SET); + if (rc < 0) { + pr_err("qpnp adc request conversion failed\n"); + return rc; + } + + return 0; +} +EXPORT_SYMBOL(qpnp_vadc_configure); + +static int32_t qpnp_vadc_read_conversion_result(int32_t *data) +{ + uint8_t rslt_lsb, rslt_msb; + int rc = 0; + + rc = qpnp_vadc_read_reg(QPNP_VADC_DATA0, &rslt_lsb); + if (rc < 0) { + pr_err("qpnp adc result read failed for data0 with %d\n", rc); + return rc; + } + + rc = qpnp_vadc_read_reg(QPNP_VADC_DATA1, &rslt_msb); + if (rc < 0) { + pr_err("qpnp adc result read failed for data1 with %d\n", rc); + return rc; + } + + *data = (rslt_msb << 8) | rslt_lsb; + + rc = qpnp_vadc_check_result(data); + if (rc < 0) { + pr_err("VADC data check failed\n"); + return rc; + } + + return 0; +} + +static int32_t qpnp_vadc_read_status(int mode_sel) +{ + u8 status1, status2, status2_conv_seq_state; + u8 status_err = QPNP_VADC_CONV_TIMEOUT_ERR; + int rc; + + switch (mode_sel) { + case (ADC_OP_CONVERSION_SEQUENCER << QPNP_VADC_OP_MODE_SHIFT): + rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1); + if (rc) { + pr_err("qpnp_vadc read mask interrupt failed\n"); + return rc; + } + + rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS2, &status2); + if (rc) { + pr_err("qpnp_vadc read mask interrupt failed\n"); + return rc; + } + + if (!(status2 & ~QPNP_VADC_STATUS2_CONV_SEQ_TIMEOUT_STS) && + (status1 & (~QPNP_VADC_STATUS1_REQ_STS | + QPNP_VADC_STATUS1_EOC))) { + rc = status_err; + return rc; + } + + status2_conv_seq_state = status2 >> + QPNP_VADC_STATUS2_CONV_SEQ_STATE_SHIFT; + if (status2_conv_seq_state != ADC_CONV_SEQ_IDLE) { + pr_err("qpnp vadc seq error with status %d\n", + status2); + rc = -EINVAL; + return rc; + } + } + + return 0; +} + +static void qpnp_vadc_work(struct work_struct *work) +{ + struct qpnp_vadc_drv *vadc = qpnp_vadc; + int rc; + + rc = qpnp_vadc_write_reg(QPNP_VADC_INT_CLR, QPNP_VADC_INT_EOC_BIT); + if (rc) + pr_err("qpnp_vadc clear mask interrupt failed with %d\n", rc); + + complete(&vadc->adc->adc_rslt_completion); + + return; +} +DECLARE_WORK(trigger_completion_work, qpnp_vadc_work); + +static irqreturn_t qpnp_vadc_isr(int irq, void *dev_id) +{ + schedule_work(&trigger_completion_work); + + return IRQ_HANDLED; +} + +static uint32_t qpnp_vadc_calib_device(void) +{ + struct qpnp_vadc_drv *vadc = qpnp_vadc; + struct qpnp_vadc_amux_properties conv; + int rc, calib_read_1, calib_read_2; + u8 status1 = 0; + + conv.amux_channel = REF_125V; + conv.decimation = DECIMATION_TYPE2; + conv.mode_sel = ADC_OP_NORMAL_MODE << QPNP_VADC_OP_MODE_SHIFT; + conv.hw_settle_time = ADC_CHANNEL_HW_SETTLE_DELAY_0US; + conv.fast_avg_setup = ADC_FAST_AVG_SAMPLE_1; + + rc = qpnp_vadc_configure(&conv); + if (rc) { + pr_err("qpnp_vadc configure failed with %d\n", rc); + goto calib_fail; + } + + while (status1 != (~QPNP_VADC_STATUS1_REQ_STS | + QPNP_VADC_STATUS1_EOC)) { + rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1); + if (rc < 0) + return rc; + usleep_range(QPNP_VADC_CONV_TIME_MIN, + QPNP_VADC_CONV_TIME_MAX); + } + + rc = qpnp_vadc_read_conversion_result(&calib_read_1); + if (rc) { + pr_err("qpnp adc read adc failed with %d\n", rc); + goto calib_fail; + } + + conv.amux_channel = REF_625MV; + conv.decimation = DECIMATION_TYPE2; + conv.mode_sel = ADC_OP_NORMAL_MODE << QPNP_VADC_OP_MODE_SHIFT; + conv.hw_settle_time = ADC_CHANNEL_HW_SETTLE_DELAY_0US; + conv.fast_avg_setup = ADC_FAST_AVG_SAMPLE_1; + rc = qpnp_vadc_configure(&conv); + if (rc) { + pr_err("qpnp adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (status1 != (~QPNP_VADC_STATUS1_REQ_STS | + QPNP_VADC_STATUS1_EOC)) { + rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1); + if (rc < 0) + return rc; + usleep_range(QPNP_VADC_CONV_TIME_MIN, + QPNP_VADC_CONV_TIME_MAX); + } + + rc = qpnp_vadc_read_conversion_result(&calib_read_1); + if (rc) { + pr_err("qpnp adc read adc failed with %d\n", rc); + goto calib_fail; + } + + vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_ABSOLUTE].dy = + (calib_read_1 - calib_read_2); + vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_ABSOLUTE].dx + = QPNP_ADC_625_UV; + vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_ABSOLUTE].adc_vref = + calib_read_1; + vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_ABSOLUTE].adc_gnd = + calib_read_2; + /* Ratiometric Calibration */ + conv.amux_channel = VDD_VADC; + conv.decimation = DECIMATION_TYPE2; + conv.mode_sel = ADC_OP_NORMAL_MODE << QPNP_VADC_OP_MODE_SHIFT; + conv.hw_settle_time = ADC_CHANNEL_HW_SETTLE_DELAY_0US; + conv.fast_avg_setup = ADC_FAST_AVG_SAMPLE_1; + rc = qpnp_vadc_configure(&conv); + if (rc) { + pr_err("qpnp adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (status1 != (~QPNP_VADC_STATUS1_REQ_STS | + QPNP_VADC_STATUS1_EOC)) { + rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1); + if (rc < 0) + return rc; + usleep_range(QPNP_VADC_CONV_TIME_MIN, + QPNP_VADC_CONV_TIME_MAX); + } + + rc = qpnp_vadc_read_conversion_result(&calib_read_1); + if (rc) { + pr_err("qpnp adc read adc failed with %d\n", rc); + goto calib_fail; + } + + conv.amux_channel = VDD_VADC; + conv.decimation = DECIMATION_TYPE2; + conv.mode_sel = ADC_OP_NORMAL_MODE << QPNP_VADC_OP_MODE_SHIFT; + conv.hw_settle_time = ADC_CHANNEL_HW_SETTLE_DELAY_0US; + conv.fast_avg_setup = ADC_FAST_AVG_SAMPLE_1; + rc = qpnp_vadc_configure(&conv); + if (rc) { + pr_err("qpnp adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (status1 != (~QPNP_VADC_STATUS1_REQ_STS | + QPNP_VADC_STATUS1_EOC)) { + rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1); + if (rc < 0) + return rc; + usleep_range(QPNP_VADC_CONV_TIME_MIN, + QPNP_VADC_CONV_TIME_MAX); + } + + rc = qpnp_vadc_read_conversion_result(&calib_read_1); + if (rc) { + pr_err("qpnp adc read adc failed with %d\n", rc); + goto calib_fail; + } + + vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_RATIOMETRIC].dy = + (calib_read_1 - calib_read_2); + vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_RATIOMETRIC].dx = + vadc->adc->adc_prop->adc_vdd_reference; + vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_RATIOMETRIC].adc_vref = + calib_read_1; + vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_RATIOMETRIC].adc_gnd = + calib_read_2; + +calib_fail: + return rc; +} + +int32_t qpnp_vadc_conv_seq_request(enum qpnp_vadc_trigger trigger_channel, + enum qpnp_vadc_channels channel, + struct qpnp_vadc_result *result) +{ + struct qpnp_vadc_drv *vadc = qpnp_vadc; + int rc, scale_type, amux_prescaling; + + if (!vadc->vadc_init_calib) { + rc = qpnp_vadc_calib_device(); + if (rc) { + pr_err("Calibration failed\n"); + return rc; + } else + vadc->vadc_init_calib = true; + } + + mutex_lock(&vadc->adc->adc_lock); + + rc = qpnp_vadc_enable(true); + if (rc) + goto fail_unlock; + + vadc->adc->amux_prop->amux_channel = channel; + vadc->adc->amux_prop->decimation = + vadc->adc->adc_channels[channel].adc_decimation; + vadc->adc->amux_prop->hw_settle_time = + vadc->adc->adc_channels[channel].hw_settle_time; + vadc->adc->amux_prop->fast_avg_setup = + vadc->adc->adc_channels[channel].fast_avg_setup; + + if (trigger_channel < ADC_SEQ_NONE) + vadc->adc->amux_prop->mode_sel = (ADC_OP_CONVERSION_SEQUENCER + << QPNP_VADC_OP_MODE_SHIFT); + else if (trigger_channel == ADC_SEQ_NONE) + vadc->adc->amux_prop->mode_sel = (ADC_OP_NORMAL_MODE + << QPNP_VADC_OP_MODE_SHIFT); + else { + pr_err("Invalid trigger channel:%d\n", trigger_channel); + goto fail; + } + + vadc->adc->amux_prop->trigger_channel = trigger_channel; + + rc = qpnp_vadc_configure(vadc->adc->amux_prop); + if (rc) { + pr_info("qpnp vadc configure failed with %d\n", rc); + goto fail; + } + + wait_for_completion(&vadc->adc->adc_rslt_completion); + + if (trigger_channel < ADC_SEQ_NONE) { + rc = qpnp_vadc_read_status(vadc->adc->amux_prop->mode_sel); + if (rc) + pr_info("Conversion sequence timed out - %d\n", rc); + } + + rc = qpnp_vadc_read_conversion_result(&result->adc_code); + if (rc) { + pr_info("qpnp vadc read adc code failed with %d\n", rc); + goto fail; + } + + amux_prescaling = vadc->adc->adc_channels[channel].chan_path_prescaling; + + vadc->adc->amux_prop->chan_prop->offset_gain_numerator = + qpnp_vadc_amux_scaling_ratio[amux_prescaling].num; + vadc->adc->amux_prop->chan_prop->offset_gain_denominator = + qpnp_vadc_amux_scaling_ratio[amux_prescaling].den; + + scale_type = vadc->adc->adc_channels[channel].adc_scale_fn; + if (scale_type >= SCALE_NONE) { + rc = -EBADF; + goto fail; + } + + vadc_scale_fn[scale_type].chan(result->adc_code, + vadc->adc->adc_prop, vadc->adc->amux_prop->chan_prop, result); + +fail: + rc = qpnp_vadc_enable(false); + if (rc) + pr_err("Disable ADC failed during configuration\n"); + +fail_unlock: + mutex_unlock(&vadc->adc->adc_lock); + + return rc; +} +EXPORT_SYMBOL(qpnp_vadc_conv_seq_request); + +int32_t qpnp_vadc_read(enum qpnp_vadc_channels channel, + struct qpnp_vadc_result *result) +{ + return qpnp_vadc_conv_seq_request(ADC_SEQ_NONE, + channel, result); +} +EXPORT_SYMBOL_GPL(qpnp_vadc_read); + +static ssize_t qpnp_adc_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct qpnp_vadc_result result; + int rc = -1; + + rc = qpnp_vadc_read(attr->index, &result); + + if (rc) + return 0; + + return snprintf(buf, QPNP_ADC_HWMON_NAME_LENGTH, + "Result:%lld Raw:%d\n", result.physical, result.adc_code); +} + +static struct sensor_device_attribute qpnp_adc_attr = + SENSOR_ATTR(NULL, S_IRUGO, qpnp_adc_show, NULL, 0); + +static int32_t qpnp_vadc_init_hwmon(struct spmi_device *spmi) +{ + struct qpnp_vadc_drv *vadc = qpnp_vadc; + struct device_node *child; + struct device_node *node = spmi->dev.of_node; + int rc = 0, i = 0, channel; + + for_each_child_of_node(node, child) { + channel = vadc->adc->adc_channels[i].channel_num; + qpnp_adc_attr.index = vadc->adc->adc_channels[i].channel_num; + qpnp_adc_attr.dev_attr.attr.name = + vadc->adc->adc_channels[i].name; + sysfs_attr_init(&vadc->sens_attr[i].dev_attr.attr); + memcpy(&vadc->sens_attr[i], &qpnp_adc_attr, + sizeof(qpnp_adc_attr)); + rc = device_create_file(&spmi->dev, + &vadc->sens_attr[i].dev_attr); + if (rc) { + dev_err(&spmi->dev, + "device_create_file failed for dev %s\n", + vadc->adc->adc_channels[i].name); + goto hwmon_err_sens; + } + i++; + } + + return 0; +hwmon_err_sens: + pr_info("Init HWMON failed for qpnp_adc with %d\n", rc); + return rc; +} + +static int qpnp_vadc_probe(struct spmi_device *spmi) +{ + struct qpnp_vadc_drv *vadc; + struct qpnp_adc_drv *adc_qpnp; + struct device_node *node = spmi->dev.of_node; + struct device_node *child; + int rc, count_adc_channel_list = 0; + + if (!node) + return -EINVAL; + + if (qpnp_vadc) { + pr_err("VADC already in use\n"); + return -EBUSY; + } + + for_each_child_of_node(node, child) + count_adc_channel_list++; + + if (!count_adc_channel_list) { + pr_err("No channel listing\n"); + return -EINVAL; + } + + vadc = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_vadc_drv) + + (sizeof(struct sensor_device_attribute) * + count_adc_channel_list), GFP_KERNEL); + if (!vadc) { + dev_err(&spmi->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_qpnp = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_adc_drv), + GFP_KERNEL); + if (!adc_qpnp) { + dev_err(&spmi->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + vadc->adc = adc_qpnp; + + rc = qpnp_adc_get_devicetree_data(spmi, vadc->adc); + if (rc) { + dev_err(&spmi->dev, "failed to read device tree\n"); + return rc; + } + + rc = devm_request_irq(&spmi->dev, vadc->adc->adc_irq, + qpnp_vadc_isr, IRQF_TRIGGER_RISING, + "qpnp_vadc_interrupt", vadc); + if (rc) { + dev_err(&spmi->dev, + "failed to request adc irq with error %d\n", rc); + return rc; + } + + qpnp_vadc = vadc; + dev_set_drvdata(&spmi->dev, vadc); + rc = qpnp_vadc_init_hwmon(spmi); + if (rc) { + dev_err(&spmi->dev, "failed to initialize qpnp hwmon adc\n"); + goto fail_free_irq; + } + vadc->vadc_hwmon = hwmon_device_register(&vadc->adc->spmi->dev); + vadc->vadc_init_calib = false; + + rc = qpnp_vadc_configure_interrupt(); + if (rc) { + dev_err(&spmi->dev, "failed to configure interrupt"); + goto fail_free_irq; + } + + return 0; + +fail_free_irq: + free_irq(vadc->adc->adc_irq, vadc); + + return rc; +} + +static int qpnp_vadc_remove(struct spmi_device *spmi) +{ + struct qpnp_vadc_drv *vadc = dev_get_drvdata(&spmi->dev); + struct device_node *node = spmi->dev.of_node; + struct device_node *child; + int i = 0; + + for_each_child_of_node(node, child) { + device_remove_file(&spmi->dev, + &vadc->sens_attr[i].dev_attr); + i++; + } + free_irq(vadc->adc->adc_irq, vadc); + dev_set_drvdata(&spmi->dev, NULL); + + return 0; +} + +static const struct of_device_id qpnp_vadc_match_table[] = { + { .compatible = "qcom,qpnp-vadc", + }, + {} +}; + +static struct spmi_driver qpnp_vadc_driver = { + .driver = { + .name = "qcom,qpnp-vadc", + .of_match_table = qpnp_vadc_match_table, + }, + .probe = qpnp_vadc_probe, + .remove = qpnp_vadc_remove, +}; + +static int __init qpnp_vadc_init(void) +{ + return spmi_driver_register(&qpnp_vadc_driver); +} +module_init(qpnp_vadc_init); + +static void __exit qpnp_vadc_exit(void) +{ + spmi_driver_unregister(&qpnp_vadc_driver); +} +module_exit(qpnp_vadc_exit); + +MODULE_DESCRIPTION("QPNP PMIC Voltage ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/qpnp/qpnp-adc.h b/include/linux/qpnp/qpnp-adc.h new file mode 100644 index 000000000000..fabd0080c7d7 --- /dev/null +++ b/include/linux/qpnp/qpnp-adc.h @@ -0,0 +1,689 @@ +/* + * Copyright (c) 2012, 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. + */ +/* + * Qualcomm PMIC QPNP ADC driver header file + * + */ + +#ifndef __QPNP_ADC_H +#define __QPNP_ADC_H + +#include +#include +/** + * enum qpnp_vadc_channels - QPNP AMUX arbiter channels + */ +enum qpnp_vadc_channels { + USBIN = 0, + DCIN, + VCHG_SNS, + SPARE1_03, + SPARE2_03, + VCOIN, + VBAT_SNS, + VSYS, + DIE_TEMP, + REF_625MV, + REF_125V, + CHG_TEMP, + SPARE1, + SPARE2, + GND_REF, + VDD_VADC, + P_MUX1_1_1, + P_MUX2_1_1, + P_MUX3_1_1, + P_MUX4_1_1, + P_MUX5_1_1, + P_MUX6_1_1, + P_MUX7_1_1, + P_MUX8_1_1, + P_MUX9_1_1, + P_MUX10_1_1, + P_MUX11_1_1, + P_MUX12_1_1, + P_MUX13_1_1, + P_MUX14_1_1, + P_MUX15_1_1, + P_MUX16_1_1, + P_MUX1_1_3, + P_MUX2_1_3, + P_MUX3_1_3, + P_MUX4_1_3, + P_MUX5_1_3, + P_MUX6_1_3, + P_MUX7_1_3, + P_MUX8_1_3, + P_MUX9_1_3, + P_MUX10_1_3, + P_MUX11_1_3, + P_MUX12_1_3, + P_MUX13_1_3, + P_MUX14_1_3, + P_MUX15_1_3, + P_MUX16_1_3, + LR_MUX1_BATT_THERM, + LR_MUX2_BAT_ID, + LR_MUX3_XO_THERM, + LR_MUX4_AMUX_THM1, + LR_MUX5_AMUX_THM2, + LR_MUX6_AMUX_THM3, + LR_MUX7_HW_ID, + LR_MUX8_AMUX_THM4, + LR_MUX9_AMUX_THM5, + LR_MUX10_USB_ID, + AMUX_PU1, + AMUX_PU2, + LR_MUX3_BUF_XO_THERM_BUF, + LR_MUX1_PU1_BAT_THERM, + LR_MUX2_PU1_BAT_ID, + LR_MUX3_PU1_XO_THERM, + LR_MUX4_PU1_AMUX_THM1, + LR_MUX5_PU1_AMUX_THM2, + LR_MUX6_PU1_AMUX_THM3, + LR_MUX7_PU1_AMUX_HW_ID, + LR_MUX8_PU1_AMUX_THM4, + LR_MUX9_PU1_AMUX_THM5, + LR_MUX10_PU1_AMUX_USB_ID, + LR_MUX3_BUF_PU1_XO_THERM_BUF, + LR_MUX1_PU2_BAT_THERM, + LR_MUX2_PU2_BAT_ID, + LR_MUX3_PU2_XO_THERM, + LR_MUX4_PU2_AMUX_THM1, + LR_MUX5_PU2_AMUX_THM2, + LR_MUX6_PU2_AMUX_THM3, + LR_MUX7_PU2_AMUX_HW_ID, + LR_MUX8_PU2_AMUX_THM4, + LR_MUX9_PU2_AMUX_THM5, + LR_MUX10_PU2_AMUX_USB_ID, + LR_MUX3_BUF_PU2_XO_THERM_BUF, + LR_MUX1_PU1_PU2_BAT_THERM, + LR_MUX2_PU1_PU2_BAT_ID, + LR_MUX3_PU1_PU2_XO_THERM, + LR_MUX4_PU1_PU2_AMUX_THM1, + LR_MUX5_PU1_PU2_AMUX_THM2, + LR_MUX6_PU1_PU2_AMUX_THM3, + LR_MUX7_PU1_PU2_AMUX_HW_ID, + LR_MUX8_PU1_PU2_AMUX_THM4, + LR_MUX9_PU1_PU2_AMUX_THM5, + LR_MUX10_PU1_PU2_AMUX_USB_ID, + LR_MUX3_BUF_PU1_PU2_XO_THERM_BUF, + ALL_OFF, + ADC_MAX_NUM, +}; + +#define QPNP_ADC_625_UV 625000 + +/** + * enum qpnp_adc_decimation_type - Sampling rate supported. + * %DECIMATION_TYPE1: 512 + * %DECIMATION_TYPE2: 1K + * %DECIMATION_TYPE3: 2K + * %DECIMATION_TYPE4: 4k + * %DECIMATION_NONE: Do not use this Sampling type. + * + * The Sampling rate is specific to each channel of the QPNP ADC arbiter. + */ +enum qpnp_adc_decimation_type { + DECIMATION_TYPE1 = 0, + DECIMATION_TYPE2, + DECIMATION_TYPE3, + DECIMATION_TYPE4, + DECIMATION_NONE, +}; + +/** + * enum qpnp_adc_calib_type - QPNP ADC Calibration type. + * %ADC_CALIB_ABSOLUTE: Use 625mV and 1.25V reference channels. + * %ADC_CALIB_RATIOMETRIC: Use reference Voltage/GND. + * %ADC_CALIB_CONFIG_NONE: Do not use this calibration type. + * + * Use the input reference voltage depending on the calibration type + * to calcluate the offset and gain parameters. The calibration is + * specific to each channel of the QPNP ADC. + */ +enum qpnp_adc_calib_type { + CALIB_ABSOLUTE = 0, + CALIB_RATIOMETRIC, + CALIB_NONE, +}; + +/** + * enum qpnp_adc_channel_scaling_param - pre-scaling AMUX ratio. + * %CHAN_PATH_SCALING1: ratio of {1, 1} + * %CHAN_PATH_SCALING2: ratio of {1, 3} + * %CHAN_PATH_SCALING3: ratio of {1, 4} + * %CHAN_PATH_SCALING4: ratio of {1, 6} + * %CHAN_PATH_NONE: Do not use this pre-scaling ratio type. + * + * The pre-scaling is applied for signals to be within the voltage range + * of the ADC. + */ +enum qpnp_adc_channel_scaling_param { + PATH_SCALING1 = 0, + PATH_SCALING2, + PATH_SCALING3, + PATH_SCALING4, + PATH_SCALING_NONE, +}; + +/** + * enum qpnp_adc_scale_fn_type - Scaling function for pm8921 pre calibrated + * digital data relative to ADC reference. + * %ADC_SCALE_DEFAULT: Default scaling to convert raw adc code to voltage. + * %ADC_SCALE_BATT_THERM: Conversion to temperature based on btm parameters. + * %ADC_SCALE_PMIC_THERM: Returns result in milli degree's Centigrade. + * %ADC_SCALE_XTERN_CHGR_CUR: Returns current across 0.1 ohm resistor. + * %ADC_SCALE_XOTHERM: Returns XO thermistor voltage in degree's Centigrade. + * %ADC_SCALE_NONE: Do not use this scaling type. + */ +enum qpnp_adc_scale_fn_type { + SCALE_DEFAULT = 0, + SCALE_BATT_THERM, + SCALE_PA_THERM, + SCALE_PMIC_THERM, + SCALE_XOTHERM, + SCALE_NONE, +}; + +/** + * enum qpnp_adc_fast_avg_ctl - Provides ability to obtain single result + * from the ADC that is an average of multiple measurement + * samples. Select number of samples for use in fast + * average mode (i.e. 2 ^ value). + * %ADC_FAST_AVG_SAMPLE_1: 0x0 = 1 + * %ADC_FAST_AVG_SAMPLE_2: 0x1 = 2 + * %ADC_FAST_AVG_SAMPLE_4: 0x2 = 4 + * %ADC_FAST_AVG_SAMPLE_8: 0x3 = 8 + * %ADC_FAST_AVG_SAMPLE_16: 0x4 = 16 + * %ADC_FAST_AVG_SAMPLE_32: 0x5 = 32 + * %ADC_FAST_AVG_SAMPLE_64: 0x6 = 64 + * %ADC_FAST_AVG_SAMPLE_128: 0x7 = 128 + * %ADC_FAST_AVG_SAMPLE_256: 0x8 = 256 + * %ADC_FAST_AVG_SAMPLE_512: 0x9 = 512 + */ +enum qpnp_adc_fast_avg_ctl { + ADC_FAST_AVG_SAMPLE_1 = 0, + ADC_FAST_AVG_SAMPLE_2, + ADC_FAST_AVG_SAMPLE_4, + ADC_FAST_AVG_SAMPLE_8, + ADC_FAST_AVG_SAMPLE_16, + ADC_FAST_AVG_SAMPLE_32, + ADC_FAST_AVG_SAMPLE_64, + ADC_FAST_AVG_SAMPLE_128, + ADC_FAST_AVG_SAMPLE_256, + ADC_FAST_AVG_SAMPLE_512, + ADC_FAST_AVG_SAMPLE_NONE, +}; + +/** + * enum qpnp_adc_hw_settle_time - Time between AMUX getting configured and + * the ADC starting conversion. Delay = 100us * value for + * value < 11 and 2ms * (value - 10) otherwise. + * %ADC_CHANNEL_HW_SETTLE_DELAY_0US: 0us + * %ADC_CHANNEL_HW_SETTLE_DELAY_100US: 100us + * %ADC_CHANNEL_HW_SETTLE_DELAY_200US: 200us + * %ADC_CHANNEL_HW_SETTLE_DELAY_300US: 300us + * %ADC_CHANNEL_HW_SETTLE_DELAY_400US: 400us + * %ADC_CHANNEL_HW_SETTLE_DELAY_500US: 500us + * %ADC_CHANNEL_HW_SETTLE_DELAY_600US: 600us + * %ADC_CHANNEL_HW_SETTLE_DELAY_700US: 700us + * %ADC_CHANNEL_HW_SETTLE_DELAY_800US: 800us + * %ADC_CHANNEL_HW_SETTLE_DELAY_900US: 900us + * %ADC_CHANNEL_HW_SETTLE_DELAY_1MS: 1ms + * %ADC_CHANNEL_HW_SETTLE_DELAY_2MS: 2ms + * %ADC_CHANNEL_HW_SETTLE_DELAY_4MS: 4ms + * %ADC_CHANNEL_HW_SETTLE_DELAY_6MS: 6ms + * %ADC_CHANNEL_HW_SETTLE_DELAY_8MS: 8ms + * %ADC_CHANNEL_HW_SETTLE_DELAY_10MS: 10ms + * %ADC_CHANNEL_HW_SETTLE_NONE + */ +enum qpnp_adc_hw_settle_time { + ADC_CHANNEL_HW_SETTLE_DELAY_0US = 0, + ADC_CHANNEL_HW_SETTLE_DELAY_100US, + ADC_CHANNEL_HW_SETTLE_DELAY_2000US, + ADC_CHANNEL_HW_SETTLE_DELAY_300US, + ADC_CHANNEL_HW_SETTLE_DELAY_400US, + ADC_CHANNEL_HW_SETTLE_DELAY_500US, + ADC_CHANNEL_HW_SETTLE_DELAY_600US, + ADC_CHANNEL_HW_SETTLE_DELAY_700US, + ADC_CHANNEL_HW_SETTLE_DELAY_800US, + ADC_CHANNEL_HW_SETTLE_DELAY_900US, + ADC_CHANNEL_HW_SETTLE_DELAY_1MS, + ADC_CHANNEL_HW_SETTLE_DELAY_2MS, + ADC_CHANNEL_HW_SETTLE_DELAY_4MS, + ADC_CHANNEL_HW_SETTLE_DELAY_6MS, + ADC_CHANNEL_HW_SETTLE_DELAY_8MS, + ADC_CHANNEL_HW_SETTLE_DELAY_10MS, + ADC_CHANNEL_HW_SETTLE_NONE, +}; + +/** + * enum qpnp_vadc_mode_sel - Selects the basic mode of operation. + * - The normal mode is used for single measurement. + * - The Conversion sequencer is used to trigger an + * ADC read when a HW trigger is selected. + * - The measurement interval performs a single or + * continous measurement at a specified interval/delay. + * %ADC_OP_NORMAL_MODE : Normal mode used for single measurement. + * %ADC_OP_CONVERSION_SEQUENCER : Conversion sequencer used to trigger + * an ADC read on a HW supported trigger. + * Refer to enum qpnp_vadc_trigger for + * supported HW triggers. + * %ADC_OP_MEASUREMENT_INTERVAL : The measurement interval performs a + * single or continous measurement after a specified delay. + * For delay look at qpnp_adc_meas_timer. + */ +enum qpnp_vadc_mode_sel { + ADC_OP_NORMAL_MODE = 0, + ADC_OP_CONVERSION_SEQUENCER, + ADC_OP_MEASUREMENT_INTERVAL, + ADC_OP_MODE_NONE, +}; + +/** + * enum qpnp_vadc_trigger - Select the HW trigger to be used while + * measuring the ADC reading. + * %ADC_GSM_PA_ON : GSM power amplifier on. + * %ADC_TX_GTR_THRES : Transmit power greater than threshold. + * %ADC_CAMERA_FLASH_RAMP : Flash ramp up done. + * %ADC_DTEST : DTEST. + */ +enum qpnp_vadc_trigger { + ADC_GSM_PA_ON = 0, + ADC_TX_GTR_THRES, + ADC_CAMERA_FLASH_RAMP, + ADC_DTEST, + ADC_SEQ_NONE, +}; + +/** + * enum qpnp_vadc_conv_seq_timeout - Select delay (0 to 15ms) from + * conversion request to triggering conversion sequencer + * hold off time. + */ +enum qpnp_vadc_conv_seq_timeout { + ADC_CONV_SEQ_TIMEOUT_0MS = 0, + ADC_CONV_SEQ_TIMEOUT_1MS, + ADC_CONV_SEQ_TIMEOUT_2MS, + ADC_CONV_SEQ_TIMEOUT_3MS, + ADC_CONV_SEQ_TIMEOUT_4MS, + ADC_CONV_SEQ_TIMEOUT_5MS, + ADC_CONV_SEQ_TIMEOUT_6MS, + ADC_CONV_SEQ_TIMEOUT_7MS, + ADC_CONV_SEQ_TIMEOUT_8MS, + ADC_CONV_SEQ_TIMEOUT_9MS, + ADC_CONV_SEQ_TIMEOUT_10MS, + ADC_CONV_SEQ_TIMEOUT_11MS, + ADC_CONV_SEQ_TIMEOUT_12MS, + ADC_CONV_SEQ_TIMEOUT_13MS, + ADC_CONV_SEQ_TIMEOUT_14MS, + ADC_CONV_SEQ_TIMEOUT_15MS, + ADC_CONV_SEQ_TIMEOUT_NONE, +}; + +/** + * enum qpnp_adc_conv_seq_holdoff - Select delay from conversion + * trigger signal (i.e. adc_conv_seq_trig) transition + * to ADC enable. Delay = 25us * (value + 1). + */ +enum qpnp_adc_conv_seq_holdoff { + ADC_SEQ_HOLD_25US = 0, + ADC_SEQ_HOLD_50US, + ADC_SEQ_HOLD_75US, + ADC_SEQ_HOLD_100US, + ADC_SEQ_HOLD_125US, + ADC_SEQ_HOLD_150US, + ADC_SEQ_HOLD_175US, + ADC_SEQ_HOLD_200US, + ADC_SEQ_HOLD_225US, + ADC_SEQ_HOLD_250US, + ADC_SEQ_HOLD_275US, + ADC_SEQ_HOLD_300US, + ADC_SEQ_HOLD_325US, + ADC_SEQ_HOLD_350US, + ADC_SEQ_HOLD_375US, + ADC_SEQ_HOLD_400US, + ADC_SEQ_HOLD_NONE, +}; + +/** + * enum qpnp_adc_conv_seq_state - Conversion sequencer operating state + * %ADC_CONV_SEQ_IDLE : Sequencer is in idle. + * %ADC_CONV_TRIG_RISE : Waiting for rising edge trigger. + * %ADC_CONV_TRIG_HOLDOFF : Waiting for rising trigger hold off time. + * %ADC_CONV_MEAS_RISE : Measuring selected ADC signal. + * %ADC_CONV_TRIG_FALL : Waiting for falling trigger edge. + * %ADC_CONV_FALL_HOLDOFF : Waiting for falling trigger hold off time. + * %ADC_CONV_MEAS_FALL : Measuring selected ADC signal. + * %ADC_CONV_ERROR : Aberrant Hardware problem. + */ +enum qpnp_adc_conv_seq_state { + ADC_CONV_SEQ_IDLE = 0, + ADC_CONV_TRIG_RISE, + ADC_CONV_TRIG_HOLDOFF, + ADC_CONV_MEAS_RISE, + ADC_CONV_TRIG_FALL, + ADC_CONV_FALL_HOLDOFF, + ADC_CONV_MEAS_FALL, + ADC_CONV_ERROR, + ADC_CONV_NONE, +}; + +/** + * enum qpnp_adc_meas_timer - Selects the measurement interval time. + * If value = 0, use 0ms else use 2^(value + 4)/ 32768). + * %ADC_MEAS_INTERVAL_0MS : 0ms + * %ADC_MEAS_INTERVAL_1P0MS : 1ms + * %ADC_MEAS_INTERVAL_2P0MS : 2ms + * %ADC_MEAS_INTERVAL_3P9MS : 3.9ms + * %ADC_MEAS_INTERVAL_7P8MS : 7.8ms + * %ADC_MEAS_INTERVAL_15P6MS : 15.6ms + * %ADC_MEAS_INTERVAL_31P3MS : 31.3ms + * %ADC_MEAS_INTERVAL_62P5MS : 62.5ms + * %ADC_MEAS_INTERVAL_125MS : 125ms + * %ADC_MEAS_INTERVAL_250MS : 250ms + * %ADC_MEAS_INTERVAL_500MS : 500ms + * %ADC_MEAS_INTERVAL_1S : 1seconds + * %ADC_MEAS_INTERVAL_2S : 2seconds + * %ADC_MEAS_INTERVAL_4S : 4seconds + * %ADC_MEAS_INTERVAL_8S : 8seconds + * %ADC_MEAS_INTERVAL_16S: 16seconds + */ +enum qpnp_adc_meas_timer { + ADC_MEAS_INTERVAL_0MS = 0, + ADC_MEAS_INTERVAL_1P0MS, + ADC_MEAS_INTERVAL_2P0MS, + ADC_MEAS_INTERVAL_3P9MS, + ADC_MEAS_INTERVAL_7P8MS, + ADC_MEAS_INTERVAL_15P6MS, + ADC_MEAS_INTERVAL_31P3MS, + ADC_MEAS_INTERVAL_62P5MS, + ADC_MEAS_INTERVAL_125MS, + ADC_MEAS_INTERVAL_250MS, + ADC_MEAS_INTERVAL_500MS, + ADC_MEAS_INTERVAL_1S, + ADC_MEAS_INTERVAL_2S, + ADC_MEAS_INTERVAL_4S, + ADC_MEAS_INTERVAL_8S, + ADC_MEAS_INTERVAL_16S, + ADC_MEAS_INTERVAL_NONE, +}; + +/** + * enum qpnp_adc_meas_interval_op_ctl - Select operating mode. + * %ADC_MEAS_INTERVAL_OP_SINGLE : Conduct single measurement at specified time + * delay. + * %ADC_MEAS_INTERVAL_OP_CONTINUOUS : Make measurements at measurement interval + * times. + */ +enum qpnp_adc_meas_interval_op_ctl { + ADC_MEAS_INTERVAL_OP_SINGLE = 0, + ADC_MEAS_INTERVAL_OP_CONTINUOUS, + ADC_MEAS_INTERVAL_OP_NONE, +}; + +/** + * struct qpnp_vadc_linear_graph - Represent ADC characteristics. + * @dy: Numerator slope to calculate the gain. + * @dx: Denominator slope to calculate the gain. + * @adc_vref: A/D word of the voltage reference used for the channel. + * @adc_gnd: A/D word of the ground reference used for the channel. + * + * Each ADC device has different offset and gain parameters which are computed + * to calibrate the device. + */ +struct qpnp_vadc_linear_graph { + int64_t dy; + int64_t dx; + int64_t adc_vref; + int64_t adc_gnd; +}; + +/** + * struct qpnp_vadc_map_pt - Map the graph representation for ADC channel + * @x: Represent the ADC digitized code. + * @y: Represent the physical data which can be temperature, voltage, + * resistance. + */ +struct qpnp_vadc_map_pt { + int32_t x; + int32_t y; +}; + +/** + * struct qpnp_vadc_scaling_ratio - Represent scaling ratio for adc input. + * @num: Numerator scaling parameter. + * @den: Denominator scaling parameter. + */ +struct qpnp_vadc_scaling_ratio { + int32_t num; + int32_t den; +}; + +/** + * struct qpnp_adc_properties - Represent the ADC properties. + * @adc_reference: Reference voltage for QPNP ADC. + * @bitresolution: ADC bit resolution for QPNP ADC. + * @biploar: Polarity for QPNP ADC. + */ +struct qpnp_adc_properties { + uint32_t adc_vdd_reference; + uint32_t bitresolution; + bool bipolar; +}; + +/** + * struct qpnp_vadc_chan_properties - Represent channel properties of the ADC. + * @offset_gain_numerator: The inverse numerator of the gain applied to the + * input channel. + * @offset_gain_denominator: The inverse denominator of the gain applied to the + * input channel. + * @adc_graph: ADC graph for the channel of struct type qpnp_adc_linear_graph. + */ +struct qpnp_vadc_chan_properties { + uint32_t offset_gain_numerator; + uint32_t offset_gain_denominator; + struct qpnp_vadc_linear_graph adc_graph[2]; +}; + +/** + * struct qpnp_adc_result - Represent the result of the QPNP ADC. + * @chan: The channel number of the requested conversion. + * @adc_code: The pre-calibrated digital output of a given ADC relative to the + * the ADC reference. + * @measurement: In units specific for a given ADC; most ADC uses reference + * voltage but some ADC uses reference current. This measurement + * here is a number relative to a reference of a given ADC. + * @physical: The data meaningful for each individual channel whether it is + * voltage, current, temperature, etc. + * All voltage units are represented in micro - volts. + * -Battery temperature units are represented as 0.1 DegC. + * -PA Therm temperature units are represented as DegC. + * -PMIC Die temperature units are represented as 0.001 DegC. + */ +struct qpnp_vadc_result { + uint32_t chan; + int32_t adc_code; + int64_t measurement; + int64_t physical; +}; + +/** + * struct qpnp_adc_amux - AMUX properties for individual channel + * @name: Channel string name. + * @channel_num: Channel in integer used from qpnp_adc_channels. + * @chan_path_prescaling: Channel scaling performed on the input signal. + * @adc_decimation: Sampling rate desired for the channel. + * adc_scale_fn: Scaling function to convert to the data meaningful for + * each individual channel whether it is voltage, current, + * temperature, etc and compensates the channel properties. + */ +struct qpnp_vadc_amux { + char *name; + enum qpnp_vadc_channels channel_num; + enum qpnp_adc_channel_scaling_param chan_path_prescaling; + enum qpnp_adc_decimation_type adc_decimation; + enum qpnp_adc_scale_fn_type adc_scale_fn; + enum qpnp_adc_fast_avg_ctl fast_avg_setup; + enum qpnp_adc_hw_settle_time hw_settle_time; +}; + +/** + * struct qpnp_vadc_scaling_ratio + * + */ +static const struct qpnp_vadc_scaling_ratio qpnp_vadc_amux_scaling_ratio[] = { + {1, 1}, + {1, 3}, + {1, 4}, + {1, 6}, + {1, 20} +}; + +/** + * struct qpnp_vadc_scale_fn - Scaling function prototype + * @chan: Function pointer to one of the scaling functions + * which takes the adc properties, channel properties, + * and returns the physical result + */ +struct qpnp_vadc_scale_fn { + int32_t (*chan) (int32_t, + const struct qpnp_adc_properties *, + const struct qpnp_vadc_chan_properties *, + struct qpnp_vadc_result *); +}; + +/** + * struct qpnp_adc_drv - QPNP ADC device structure. + * @spmi - spmi device for ADC peripheral. + * @offset - base offset for the ADC peripheral. + * @adc_prop - ADC properties specific to the ADC peripheral. + * @amux_prop - AMUX properties representing the ADC peripheral. + * @adc_channels - ADC channel properties for the ADC peripheral. + * @adc_irq - IRQ number that is mapped to the ADC peripheral. + * @adc_lock - ADC lock for access to the peripheral. + * @adc_rslt_completion - ADC result notification after interrupt + * is received. + */ +struct qpnp_adc_drv { + struct spmi_device *spmi; + uint8_t slave; + uint16_t offset; + struct qpnp_adc_properties *adc_prop; + struct qpnp_vadc_amux_properties *amux_prop; + struct qpnp_vadc_amux *adc_channels; + int adc_irq; + struct mutex adc_lock; + struct completion adc_rslt_completion; +}; + +/** + * struct qpnp_vadc_amux_properties - QPNP VADC amux channel property. + * @amux_channel - Refer to the qpnp_vadc_channel list. + * @decimation - Sampling rate supported for the channel. + * @mode_sel - The basic mode of operation. + * @hw_settle_time - The time between AMUX being configured and the + * start of conversion. + * @fast_avg_setup - Ability to provide single result from the ADC + * that is an average of multiple measurements. + * @trigger_channel - HW trigger channel for conversion sequencer. + * @chan_prop - Represent the channel properties of the ADC. + */ +struct qpnp_vadc_amux_properties { + uint32_t amux_channel; + uint32_t decimation; + uint32_t mode_sel; + uint32_t hw_settle_time; + uint32_t fast_avg_setup; + enum qpnp_vadc_trigger trigger_channel; + struct qpnp_vadc_chan_properties chan_prop[0]; +}; + +/* Public API */ +#if defined(CONFIG_SENSORS_QPNP_ADC_VOLTAGE) \ + || defined(CONFIG_SENSORS_QPNP_ADC_VOLTAGE_MODULE) +/** + * qpnp_vadc_read() - Performs ADC read on the channel. + * @channel: Input channel to perform the ADC read. + * @result: Structure pointer of type adc_chan_result + * in which the ADC read results are stored. + */ +int32_t qpnp_vadc_read(enum qpnp_vadc_channels channel, + struct qpnp_vadc_result *result); + +/** + * qpnp_vadc_conv_seq_request() - Performs ADC read on the conversion + * sequencer channel. + * @channel: Input channel to perform the ADC read. + * @result: Structure pointer of type adc_chan_result + * in which the ADC read results are stored. + */ +int32_t qpnp_vadc_conv_seq_request( + enum qpnp_vadc_trigger trigger_channel, + enum qpnp_vadc_channels channel, + struct qpnp_vadc_result *result); + +/** + * qpnp_vadc_check_result() - Performs check on the ADC raw code. + * @data: Data used for verifying the range of the ADC code. + */ +int32_t qpnp_vadc_check_result(int32_t *data); + +/** + * qpnp_adc_get_devicetree_data() - Abstracts the ADC devicetree data. + * @spmi: spmi ADC device. + * @adc_qpnp: spmi device tree node structure + */ +int32_t qpnp_adc_get_devicetree_data(struct spmi_device *spmi, + struct qpnp_adc_drv *adc_qpnp); + +/** + * qpnp_vadc_configure() - Configure ADC device to start conversion. + * @chan_prop: Individual channel properties for the AMUX channel. + */ +int32_t qpnp_vadc_configure( + struct qpnp_vadc_amux_properties *chan_prop); + +/** + * qpnp_adc_scale_default() - Scales the pre-calibrated digital output + * of an ADC to the ADC reference and compensates for the + * gain and offset. + * @adc_code: pre-calibrated digital ouput of the ADC. + * @adc_prop: adc properties of the qpnp adc such as bit resolution, + * reference voltage. + * @chan_prop: Individual channel properties to compensate the i/p scaling, + * slope and offset. + * @chan_rslt: Physical result to be stored. + */ +int32_t qpnp_adc_scale_default(int32_t adc_code, + const struct qpnp_adc_properties *adc_prop, + const struct qpnp_vadc_chan_properties *chan_prop, + struct qpnp_vadc_result *chan_rslt); +#else +static inline int32_t qpnp_vadc_read(uint32_t channel, + struct qpnp_vadc_result *result) +{ return -ENXIO; } +static inline int32_t qpnp_vadc_conv_seq_request( + enum qpnp_vadc_trigger trigger_channel, + enum qpnp_vadc_channels channel, + struct qpnp_vadc_result *result) +{ return -ENXIO; } +static inline int32_t qpnp_adc_scale_default(int32_t adc_code, + const struct qpnp_adc_properties *adc_prop, + const struct qpnp_adc_chan_properties *chan_prop, + struct qpnp_adc_chan_result *chan_rslt) +{ return -ENXIO; } +#endif + +#endif