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

Commit c7f857a0 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

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

parents 0d493e40 b8fb16ef
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;