Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8fb16ef authored by Jishnu Prakash's avatar Jishnu Prakash
Browse files

iio: qcom-spmi-adc5: Add support for SW calibrated ADC



Add support for reading ADC channels on a PMIC7 ADC directly from
the ADC_PBS peripheral on that PMIC instead of through PMK8350
ADC peripheral using PBS, for targets where PMK8350 does not run in
master mode.

The conversion request sequence for using ADC_PBS is similar to
the existing one for ADC7, with some differences in register
writes. In addition, SW now performs calibration in place of PBS,
for every conversion, by reading the three calibration channels,
calculating the reference values and entering them in the
calibration registers.

Change-Id: I9e47ed69e75ee2580a37195ea9063f24ffb8aac5
Signed-off-by: default avatarJishnu Prakash <jprakash@codeaurora.org>
parent c9a0e4a1
Loading
Loading
Loading
Loading
+332 −6
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -89,6 +90,24 @@
#define ADC_APP_SID_MASK			0xf
#define ADC7_CONV_TIMEOUT_MS			501

/* For ADC_PBS on PMIC7 with SW calibration */
#define ADC7_SW_CALIB_PBS_CALREF_FLAG		0x57
#define ADC7_SW_CALIB_PBS_CALREF_RDY		BIT(7)

#define ADC7_SW_CALIB_PBS_GND_REF_D0		0x58
#define ADC7_SW_CALIB_PBS_GND_REF_D1		0x59
#define ADC7_SW_CALIB_PBS_VREF_VADC_DELTA_D0	0x5a
#define ADC7_SW_CALIB_PBS_VREF_VADC_DELTA_D1	0x5b
#define ADC7_SW_CALIB_PBS_VREF_MBG_DELTA_D0	0x5c
#define ADC7_SW_CALIB_PBS_VREF_MBG_DELTA_D1	0x5d

/* For ADC_CMN on PMIC7 with SW calibration  */
#define ADC7_SW_CALIB_CMN_PBUS_WRITE_SYNC_CTL	0x4e
#define ADC7_PBUS_WRITE_SYNC_SW_CLK_REQ		BIT(2)
#define ADC7_PBUS_WRITE_SYNC_SW_CLK_REQ_MODE	BIT(1)
#define ADC7_PBUS_WRITE_SYNC_BYPASS		BIT(0)


enum adc5_cal_method {
	ADC5_NO_CAL = 0,
	ADC5_RATIOMETRIC_CAL,
@@ -134,6 +153,8 @@ struct adc5_channel_prop {
 * @regmap: SPMI ADC5 peripheral register map field.
 * @dev: SPMI ADC5 device.
 * @base: base address for the ADC peripheral.
 * @cmn_base: base address for the ADC_CMN peripheral, needed
 *	for SW calibrated ADC.
 * @nchannels: number of ADC channels.
 * @chan_props: array of ADC channel properties.
 * @iio_chans: array of IIO channels specification.
@@ -146,6 +167,7 @@ struct adc5_chip {
	struct regmap		*regmap;
	struct device		*dev;
	u16			base;
	u16			cmn_base;
	unsigned int		nchannels;
	struct adc5_channel_prop	*chan_props;
	struct iio_chan_spec	*iio_chans;
@@ -175,17 +197,50 @@ static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {

static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len)
{
	return regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
	int ret;

	ret = regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
	if (ret)
		pr_err("adc read to register %#x of length:%d failed, ret=%d\n",
			offset, len, ret);

	return ret;
}

static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len)
{
	return regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
	int ret;

	ret = regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
	if (ret)
		pr_err("adc write to register %#x of length:%d failed, ret=%d\n",
			offset, len, ret);

	return ret;
}

static int adc5_masked_write(struct adc5_chip *adc, u16 offset, u8 mask, u8 val)
{
	return regmap_update_bits(adc->regmap, adc->base + offset, mask, val);
	int ret;

	ret = regmap_update_bits(adc->regmap, adc->base + offset, mask, val);
	if (ret)
		pr_err("adc masked write to register %#x with mask:0x%x failed, ret=%d\n",
			offset, mask, ret);

	return ret;
}

static int adc5_cmn_write(struct adc5_chip *adc, u16 offset, u8 *data, int len)
{
	int ret;

	ret = regmap_bulk_write(adc->regmap, adc->cmn_base + offset, data, len);
	if (ret)
		pr_err("adc_cmn write to register %#x of length:%d failed, ret=%d\n",
			offset, len, ret);

	return ret;
}

static int adc5_prescaling_from_dt(u32 num, u32 den)
@@ -242,11 +297,11 @@ static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
	int ret;
	u8 rslt_lsb, rslt_msb;

	ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, sizeof(rslt_lsb));
	ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, 1);
	if (ret)
		return ret;

	ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, sizeof(rslt_lsb));
	ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, 1);
	if (ret)
		return ret;

@@ -385,6 +440,52 @@ static int adc7_configure(struct adc5_chip *adc,
	return ret;
}

static int adc7_sw_calib_configure(struct adc5_chip *adc,
			struct adc5_channel_prop *prop)
{
	int ret;
	u8 buf[5], val = 0;

	/* Read registers 0x42 through 0x46 */
	ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf));
	if (ret < 0)
		return ret;

	/* Digital param selection */
	adc5_update_dig_param(adc, prop, &buf[0]);

	/* Update fast average sample value */
	buf[1] &= (u8) ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK;
	buf[1] |= prop->avg_samples | ADC5_USR_FAST_AVG_CTL_EN;

	/* Select ADC channel */
	buf[2] = prop->channel;

	/* Select HW settle delay for channel */
	buf[3] &= (u8) ~ADC5_USR_HW_SETTLE_DELAY_MASK;
	buf[3] |= prop->hw_settle_time;

	/* Select ADC enable */
	buf[4] |= ADC5_USR_EN_CTL1_ADC_EN;

	if (!adc->poll_eoc)
		reinit_completion(&adc->complete);

	ret = adc5_write(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf));
	if (ret < 0)
		return ret;

	val = ADC7_PBUS_WRITE_SYNC_SW_CLK_REQ | ADC7_PBUS_WRITE_SYNC_SW_CLK_REQ_MODE;

	ret = adc5_cmn_write(adc, ADC7_SW_CALIB_CMN_PBUS_WRITE_SYNC_CTL, &val, 1);
	if (ret < 0)
		return ret;

	/* Select CONV request */
	val = ADC5_USR_CONV_REQ_REQ;
	return adc5_write(adc, ADC5_USR_CONV_REQ, &val, 1);
}

static int adc5_do_conversion(struct adc5_chip *adc,
			struct adc5_channel_prop *prop,
			struct iio_chan_spec const *chan,
@@ -494,6 +595,59 @@ static int adc7_do_conversion(struct adc5_chip *adc,
	return ret;
}

#define ADC7_SW_CALIB_CONV_TIMEOUT_MS			150
static int adc7_sw_calib_do_conversion(struct adc5_chip *adc,
			struct adc5_channel_prop *prop, u16 *adc_code_volt)
{
	int ret;
	unsigned long rc;
	u8 status = 0, val;

	mutex_lock(&adc->lock);

	ret = adc7_sw_calib_configure(adc, prop);
	if (ret) {
		pr_err("ADC configure failed with %d\n", ret);
		goto unlock;
	}

	/* No support for polling mode at present*/
	rc = wait_for_completion_timeout(&adc->complete,
					msecs_to_jiffies(ADC7_SW_CALIB_CONV_TIMEOUT_MS));
	if (!rc) {
		pr_err("Reading ADC channel %s timed out\n",
			prop->datasheet_name);
		ret = -ETIMEDOUT;
		goto unlock;
	}

	ret = adc5_read(adc, ADC5_USR_STATUS1, &status, 1);
	if (ret < 0)
		goto unlock;

	if (!(status & ADC5_USR_STATUS1_EOC)) {
		pr_err("ADC channel %s EOC bit not set, status=%#x\n",
			prop->datasheet_name, status);
		ret = -EIO;
		goto unlock;
	}

	ret = adc5_read_voltage_data(adc, adc_code_volt);
	if (ret < 0)
		goto unlock;

	val = 0;
	ret = adc5_write(adc, ADC5_USR_EN_CTL1, &val, 1);
	if (ret < 0)
		goto unlock;

	ret = adc5_cmn_write(adc, ADC7_SW_CALIB_CMN_PBUS_WRITE_SYNC_CTL, &val, 1);
unlock:
	mutex_unlock(&adc->lock);

	return ret;
}

static irqreturn_t adc5_isr(int irq, void *dev_id)
{
	struct adc5_chip *adc = dev_id;
@@ -503,6 +657,21 @@ static irqreturn_t adc5_isr(int irq, void *dev_id)
	return IRQ_HANDLED;
}

static struct adc5_channel_prop *adc7_get_channel(struct adc5_chip *adc,
						  unsigned int num)
{
	unsigned int i;

	for (i = 0; i < adc->nchannels; i++) {
		if (adc->chan_props[i].channel == num)
			return &adc->chan_props[i];
	}

	pr_err("Invalid channel %02x\n", num);

	return NULL;
}

static int adc5_of_xlate(struct iio_dev *indio_dev,
				const struct of_phandle_args *iiospec)
{
@@ -609,6 +778,118 @@ static int adc5_read_raw(struct iio_dev *indio_dev,
	return 0;
}

static int adc7_calib(struct adc5_chip *adc)
{
	int ret = 0;
	u16 gnd, vref_1p25, vref_vdd;
	u8 buf[2];
	struct adc5_channel_prop *gnd_prop, *vref_1p25_prop, *vref_vdd_prop;

	/* These channels are mandatory, they are used as reference points */
	gnd_prop = adc7_get_channel(adc, ADC7_REF_GND);
	if (!gnd_prop) {
		dev_err(adc->dev, "GND channel not defined for SW calibration\n");
		return -ENODEV;
	}

	vref_1p25_prop = adc7_get_channel(adc, ADC7_1P25VREF);
	if (!vref_1p25_prop) {
		dev_err(adc->dev, "1.25VREF channel not defined for SW calibration\n");
		return -ENODEV;
	}

	vref_vdd_prop = adc7_get_channel(adc, ADC7_VREF_VADC);
	if (!vref_vdd_prop) {
		dev_err(adc->dev, "VDD channel not defined for SW calibration\n");
		return -ENODEV;
	}

	ret = adc7_sw_calib_do_conversion(adc, gnd_prop, &gnd);
	if (ret) {
		dev_err(adc->dev, "Failed to read GND channel, ret = %d\n", ret);
		return ret;
	}

	ret = adc7_sw_calib_do_conversion(adc, vref_1p25_prop, &vref_1p25);
	if (ret) {
		dev_err(adc->dev, "Failed to read 1.25VREF channel, ret = %d\n", ret);
		return ret;
	}

	ret = adc7_sw_calib_do_conversion(adc, vref_vdd_prop, &vref_vdd);
	if (ret) {
		dev_err(adc->dev, "Failed to read VDD channel, ret = %d\n", ret);
		return ret;
	}

	buf[0] = gnd & 0xff;
	buf[1] = gnd >> 8;
	ret = adc5_write(adc, ADC7_SW_CALIB_PBS_GND_REF_D0, buf, sizeof(buf));
	if (ret)
		return ret;

	vref_vdd -= gnd;
	buf[0] = vref_vdd & 0xff;
	buf[1] = vref_vdd >> 8;
	ret = adc5_write(adc, ADC7_SW_CALIB_PBS_VREF_VADC_DELTA_D0, buf, sizeof(buf));
	if (ret)
		return ret;

	vref_1p25 -= gnd;
	buf[0] = vref_1p25 & 0xff;
	buf[1] = vref_1p25 >> 8;
	ret = adc5_write(adc, ADC7_SW_CALIB_PBS_VREF_MBG_DELTA_D0, buf, sizeof(buf));

	if (!ret)
		dev_dbg(adc->dev, "SW calibration done, gnd:0x%x vref_vdd:0x%x vref_1p25:0x%x\n",
			gnd, vref_vdd, vref_1p25);

	return ret;
}

static int adc7_sw_calib_conv(struct adc5_chip *adc, struct adc5_channel_prop *prop, int *val)
{
	int ret = 0;
	u16 adc_code_volt;

	ret = adc7_calib(adc);
	if (ret)
		return ret;

	ret = adc7_sw_calib_do_conversion(adc, prop, &adc_code_volt);
	if (ret)
		return ret;

	return qcom_adc5_hw_scale(prop->scale_fn_type,
		&adc5_prescale_ratios[prop->prescale],
		adc->data,
		adc_code_volt, val);
}

static int adc7_sw_calib_read_raw(struct iio_dev *indio_dev,
			 struct iio_chan_spec const *chan, int *val, int *val2,
			 long mask)
{
	struct adc5_chip *adc = iio_priv(indio_dev);
	struct adc5_channel_prop *prop;
	int ret;

	prop = &adc->chan_props[chan->address];

	switch (mask) {
	case IIO_CHAN_INFO_PROCESSED:
		ret = adc7_sw_calib_conv(adc, prop, val);
		if (ret)
			return ret;

		return IIO_VAL_INT;
	default:
		return -EINVAL;
	}

	return 0;
}

static const struct iio_info adc5_info = {
	.read_raw = adc5_read_raw,
	.of_xlate = adc5_of_xlate,
@@ -619,6 +900,11 @@ static const struct iio_info adc7_info = {
	.of_xlate = adc7_of_xlate,
};

static const struct iio_info adc7_sw_calib_info = {
	.read_raw = adc7_sw_calib_read_raw,
	.of_xlate = adc5_of_xlate,
};

struct adc5_channels {
	const char *datasheet_name;
	unsigned int prescale_index;
@@ -711,6 +997,8 @@ static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = {
					SCALE_HW_CALIB_DEFAULT)
	[ADC7_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 0,
					SCALE_HW_CALIB_DEFAULT)
	[ADC7_VREF_VADC]	= ADC5_CHAN_VOLT("vref_vadc", 0,
					SCALE_HW_CALIB_DEFAULT)
	[ADC7_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 1,
					SCALE_HW_CALIB_DEFAULT)
	[ADC7_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
@@ -904,6 +1192,8 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,

	if (of_property_read_bool(node, "qcom,ratiometric"))
		prop->cal_method = ADC5_RATIOMETRIC_CAL;
	else if (of_property_read_bool(node, "qcom,no-cal"))
		prop->cal_method = ADC5_NO_CAL;
	else
		prop->cal_method = ADC5_ABSOLUTE_CAL;

@@ -980,6 +1270,10 @@ static const struct of_device_id adc5_match_table[] = {
		.compatible = "qcom,spmi-adc7",
		.data = &adc7_data_pmic,
	},
	{
		.compatible = "qcom,spmi-adc7-sw-calib",
		.data = &adc7_data_pmic,
	},
	{
		.compatible = "qcom,spmi-adc-rev2",
		.data = &adc5_data_pmic_rev2,
@@ -1062,8 +1356,10 @@ static int adc5_probe(struct platform_device *pdev)
	struct adc5_chip *adc;
	struct regmap *regmap;
	const char *irq_name;
	const __be32 *prop_addr;
	int ret, irq_eoc;
	u32 reg;
	u8 val;

	regmap = dev_get_regmap(dev->parent, NULL);
	if (!regmap)
@@ -1080,13 +1376,32 @@ static int adc5_probe(struct platform_device *pdev)
	adc = iio_priv(indio_dev);
	adc->regmap = regmap;
	adc->dev = dev;
	adc->base = reg;

	prop_addr = of_get_address(dev->of_node, 0, NULL, NULL);
	if (!prop_addr) {
		pr_err("invalid IO resource\n");
		return -EINVAL;
	}
	adc->base = be32_to_cpu(*prop_addr);

	prop_addr = of_get_address(dev->of_node, 1, NULL, NULL);
	if (!prop_addr)
		pr_debug("invalid cmn resource\n");
	else
		adc->cmn_base = be32_to_cpu(*prop_addr);

	platform_set_drvdata(pdev, adc);

	if (of_device_is_compatible(node, "qcom,spmi-adc7")) {
		indio_dev->info = &adc7_info;
		adc->is_pmic7 = true;
	} else if (of_device_is_compatible(node, "qcom,spmi-adc7-sw-calib")) {
		if (!adc->cmn_base) {
			pr_err("ADC_CMN undefined\n");
			return -ENODEV;
		}
		indio_dev->info = &adc7_sw_calib_info;
		adc->is_pmic7 = true;
	} else {
		indio_dev->info = &adc5_info;
	}
@@ -1116,6 +1431,17 @@ static int adc5_probe(struct platform_device *pdev)
			return ret;
	}

	if (of_device_is_compatible(node, "qcom,spmi-adc7-sw-calib")) {
		ret = adc7_calib(adc);
		if (ret)
			return ret;

		val = ADC7_SW_CALIB_PBS_CALREF_RDY;
		ret = adc5_write(adc, ADC7_SW_CALIB_PBS_CALREF_FLAG, &val, 1);
		if (ret < 0)
			return ret;
	}

	indio_dev->dev.parent = dev;
	indio_dev->dev.of_node = node;
	indio_dev->name = pdev->name;