diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index ad435c2be706..4c9d60f6d785 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,23 @@ #define ADC_APP_SID_MASK GENMASK(3, 0) #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, @@ -135,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. @@ -147,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; @@ -159,17 +180,50 @@ struct adc5_chip { 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_read_voltage_data(struct adc5_chip *adc, u16 *data) @@ -177,11 +231,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; @@ -318,6 +372,52 @@ static int adc7_configure(struct adc5_chip *adc, return adc5_write(adc, ADC5_USR_CONV_REQ, &conv_req, 1); } +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, @@ -428,6 +528,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; +} + typedef int (*adc_do_conversion)(struct adc5_chip *adc, struct adc5_channel_prop *prop, struct iio_chan_spec const *chan, @@ -442,6 +595,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) { @@ -518,6 +686,118 @@ static int adc7_read_raw(struct iio_dev *indio_dev, mask, adc7_do_conversion); } +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, + 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, @@ -528,6 +808,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; @@ -620,6 +905,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, @@ -808,6 +1095,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; @@ -887,6 +1176,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, @@ -962,8 +1255,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; u32 reg; + u8 val; regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) @@ -980,9 +1275,27 @@ static int adc5_probe(struct platform_device *pdev) adc = iio_priv(indio_dev); adc->regmap = regmap; adc->dev = dev; - adc->base = reg; - dev_set_drvdata(&pdev->dev, adc); + 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); + + + if (of_device_is_compatible(node, "qcom,spmi-adc7-sw-calib")) { + if (!adc->cmn_base) { + pr_err("ADC_CMN undefined\n"); + return -ENODEV; + } + } init_completion(&adc->complete); mutex_init(&adc->lock); @@ -1009,6 +1322,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->name = pdev->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = adc->data->info;