Re: [PATCH v2 1/2] iio: vadc: Qualcomm SPMI PMIC voltage ADC driver

From: Stanimir Varbanov
Date: Mon Sep 15 2014 - 10:13:01 EST


Hi Jonathan,

Thanks for the review!

On 09/13/2014 08:27 PM, Jonathan Cameron wrote:
> On 13/09/14 00:27, Hartmut Knaack wrote:
>> Stanimir Varbanov schrieb, Am 11.09.2014 17:13:
>>> The voltage ADC is peripheral of Qualcomm SPMI PMIC chips. It has
>>> 15bits resolution and register space inside PMIC accessible across
>>> SPMI bus.
>>>
>>> The vadc driver registers itself through IIO interface.
>>>
>> Looks already pretty good. Things you should consider in regard of common coding style are to use the variable name ret instead of rc, since it is used in almost all adc drivers and thus makes reviewing a bit easier. Besides that, you seem to use unsigned as well as unsigned int, so to be consistent, please stick to one of them. Other comments in line.
>
> A few additional comments from me. My biggest question is whether
> you are actually making life difficult for yourself by having
> vadc_channels and vadc->channels (don't like the similar naming btw!)
> in different orders. I think you can move the ordering into the device
> tree reading code rather than doing it in lots of other places. Hence
> rather than an order based on the device tree description, put the
> data into a fixed ofer in vadc->channels.
>
> Entirely possible I'm missing something though :)
>>> Signed-off-by: Stanimir Varbanov <svarbanov@xxxxxxxxxx>
>>> Signed-off-by: Ivan T. Ivanov <iivanov@xxxxxxxxxx>
>>> ---
>>> drivers/iio/adc/Kconfig | 11 +
>>> drivers/iio/adc/Makefile | 1 +
>>> drivers/iio/adc/qcom-spmi-vadc.c | 999 +++++++++++++++++++++++++
>>> include/dt-bindings/iio/qcom,spmi-pmic-vadc.h | 119 +++
>>> 4 files changed, 1130 insertions(+), 0 deletions(-)
>>> create mode 100644 drivers/iio/adc/qcom-spmi-vadc.c
>>> create mode 100644 include/dt-bindings/iio/qcom,spmi-pmic-vadc.h

<snip>

>>> +
>>> +static int
>> Don't wrap line here.
>>> +vadc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
>>> + int *val, int *val2, long mask)
>>> +{
>>> + struct vadc_priv *vadc = iio_priv(indio_dev);
>>> + struct vadc_channel *vchan;
>>> + struct vadc_result result;
>>> + int rc;
>>> +
> It is a bit of a pitty we can't avoid this lookup. Normally I'd suggest
> putting an index in chan->address but you've already used that.
> The purpose of this is to (I think) allow you to have the private
> data stored in a random order... What is the benefit of doing that?
> (see various comments elsewhere)

So the vadc_channels array describe all possible vadc channels for all
supported PMICs from this driver. On the other side vadc->channels
pointer should contain only the channels described in DT.

Thus we need a below function to check is the current channel is active
for the current DT (current PMIC version). This is because we have in
vadc_channels the full set of channels but not every supported PMIC have
support for them.

I agree that this peace of code is not so clear. So I will try to rework
this and register to the IIO core only those channels that have channel
descriptions in DT.

Also I wonder can I use iio_chan_spec::address field as a pointer to
private structure with vadc channel properties like decimation, prescale
etc. got from DT or the default values.

>
>>> + vchan = vadc_find_channel(vadc, chan->channel);
>>> + if (!vchan)
>>> + return -EINVAL;
>>> +
>>> + if (!vadc->is_ref_measured) {
>>> + rc = vadc_measure_reference_points(vadc);
>>> + if (rc)
>>> + return rc;
>>> +
>>> + vadc->is_ref_measured = true;
>>> + }
>>> +
>>> + switch (mask) {
>>> + case IIO_CHAN_INFO_PROCESSED:
>>> + rc = vadc_do_conversion(vadc, vchan, &result.adc_code);
>>> + if (rc)
>>> + return rc;
>>> +
>>> + vadc_calibrate(vadc, vchan, &result);
>>> +
>>> + *val = result.physical;
> I'm a little suspicious here. Are the resulting values in milivolts for
> all the channels? Very handy if so, but seems a little unlikely with 15 bit
> ADC that you'd have no part of greater accuracy than a milivolt.

In fact *val is in microvolts. What is the expected unit from IIO ADC users?

>>> + rc = IIO_VAL_INT;
>> return IIO_VAL_INT;
>>> + break;
>>> + default:
>>> + rc = -EINVAL;
>>> + break;
>> Drop default case, or leave empty.
>>> + }
>>> +
>>> + return rc;
>> return -EINVAL;
>>> +}
>>> +
>>> +static const struct iio_info vadc_info = {
>>> + .read_raw = vadc_read_raw,
>>> + .driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +#define VADC_CHAN(_id, _pre) \
>>> + [VADC_##_id] = { \
>>> + .type = IIO_VOLTAGE, \
> A few of the below look to be temp sensors. If they are hardwired
> in some way to this functionality (i.e. is it on chip) then it might be
> nice to reflect this in the channel type.

There are a dedicated channels to measure temperature. Those channels
have connected thermistor but I don't think it is on chip. So the
returned adc code is in microvolts and we have a translation table to
convert measured voltage to miliCelsius. I thought that if I mark the
channel type as IIO_TEMP then the user would expect the returned units
to be miliCelsius. If that assumption is not correct I can change the
type of those channels.

>>> + .indexed = 1, \
>>> + .channel = VADC_##_id, \
>>> + .address = _pre, \
>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
>>> + .datasheet_name = __stringify(VADC_##_id), \
>>> + .scan_type = { \
>>> + .sign = 's', \
>>> + .realbits = 15, \
>>> + .storagebits = 16, \
>>> + .endianness = IIO_CPU, \
>>> + }, \
>>> + },
>>> +
>>> +static const struct iio_chan_spec vadc_channels[] = {
>>> + VADC_CHAN(USBIN, 4) /* 0x00 */
>>> + VADC_CHAN(DCIN, 4)
>>> + VADC_CHAN(VCHG_SNS, 3)
>>> + VADC_CHAN(SPARE1_03, 1)
>>> + VADC_CHAN(USB_ID_MV, 1)
>>> + VADC_CHAN(VCOIN, 1)
>>> + VADC_CHAN(VBAT_SNS, 1)
>>> + VADC_CHAN(VSYS, 1)
>>> + VADC_CHAN(DIE_TEMP, 0)
>>> + VADC_CHAN(REF_625MV, 0)
>>> + VADC_CHAN(REF_1250MV, 0)
>>> + VADC_CHAN(CHG_TEMP, 0)
>>> + VADC_CHAN(SPARE1, 0)
>>> + VADC_CHAN(SPARE2, 0)
>>> + VADC_CHAN(GND_REF, 0)
>>> + VADC_CHAN(VDD_VADC, 0) /* 0x0f */
>>> +
>>> + VADC_CHAN(P_MUX1_1_1, 0) /* 0x10 */
>>> + VADC_CHAN(P_MUX2_1_1, 0)
>>> + VADC_CHAN(P_MUX3_1_1, 0)
>>> + VADC_CHAN(P_MUX4_1_1, 0)
>>> + VADC_CHAN(P_MUX5_1_1, 0)
>>> + VADC_CHAN(P_MUX6_1_1, 0)
>>> + VADC_CHAN(P_MUX7_1_1, 0)
>>> + VADC_CHAN(P_MUX8_1_1, 0)
>>> + VADC_CHAN(P_MUX9_1_1, 0)
>>> + VADC_CHAN(P_MUX10_1_1, 0)
>>> + VADC_CHAN(P_MUX11_1_1, 0)
>>> + VADC_CHAN(P_MUX12_1_1, 0)
>>> + VADC_CHAN(P_MUX13_1_1, 0)
>>> + VADC_CHAN(P_MUX14_1_1, 0)
>>> + VADC_CHAN(P_MUX15_1_1, 0)
>>> + VADC_CHAN(P_MUX16_1_1, 0) /* 0x1f */
>>> +
>>> + VADC_CHAN(P_MUX1_1_3, 1) /* 0x20 */
>>> + VADC_CHAN(P_MUX2_1_3, 1)
>>> + VADC_CHAN(P_MUX3_1_3, 1)
>>> + VADC_CHAN(P_MUX4_1_3, 1)
>>> + VADC_CHAN(P_MUX5_1_3, 1)
>>> + VADC_CHAN(P_MUX6_1_3, 1)
>>> + VADC_CHAN(P_MUX7_1_3, 1)
>>> + VADC_CHAN(P_MUX8_1_3, 1)
>>> + VADC_CHAN(P_MUX9_1_3, 1)
>>> + VADC_CHAN(P_MUX10_1_3, 1)
>>> + VADC_CHAN(P_MUX11_1_3, 1)
>>> + VADC_CHAN(P_MUX12_1_3, 1)
>>> + VADC_CHAN(P_MUX13_1_3, 1)
>>> + VADC_CHAN(P_MUX14_1_3, 1)
>>> + VADC_CHAN(P_MUX15_1_3, 1)
>>> + VADC_CHAN(P_MUX16_1_3, 1) /* 0x2f */
>>> +
>>> + VADC_CHAN(LR_MUX1_BAT_THERM, 0) /* 0x30 */
>>> + VADC_CHAN(LR_MUX2_BAT_ID, 0)
>>> + VADC_CHAN(LR_MUX3_XO_THERM, 0)
>>> + VADC_CHAN(LR_MUX4_AMUX_THM1, 0)
>>> + VADC_CHAN(LR_MUX5_AMUX_THM2, 0)
>>> + VADC_CHAN(LR_MUX6_AMUX_THM3, 0)
>>> + VADC_CHAN(LR_MUX7_HW_ID, 0)
>>> + VADC_CHAN(LR_MUX8_AMUX_THM4, 0)
>>> + VADC_CHAN(LR_MUX9_AMUX_THM5, 0)
>>> + VADC_CHAN(LR_MUX10_USB_ID, 0)
>>> + VADC_CHAN(AMUX_PU1, 0)
>>> + VADC_CHAN(AMUX_PU2, 0)
>>> + VADC_CHAN(LR_MUX3_BUF_XO_THERM, 0) /* 0x3c */
>>> +
>>> + VADC_CHAN(LR_MUX1_PU1_BAT_THERM, 0) /* 0x70 */
>>> + VADC_CHAN(LR_MUX2_PU1_BAT_ID, 0)
>>> + VADC_CHAN(LR_MUX3_PU1_XO_THERM, 0)
>>> + VADC_CHAN(LR_MUX4_PU1_AMUX_THM1, 0)
>>> + VADC_CHAN(LR_MUX5_PU1_AMUX_THM2, 0)
>>> + VADC_CHAN(LR_MUX6_PU1_AMUX_THM3, 0)
>>> + VADC_CHAN(LR_MUX7_PU1_AMUX_HW_ID, 0)
>>> + VADC_CHAN(LR_MUX8_PU1_AMUX_THM4, 0)
>>> + VADC_CHAN(LR_MUX9_PU1_AMUX_THM5, 0)
>>> + VADC_CHAN(LR_MUX10_PU1_AMUX_USB_ID, 0) /* 0x79 */
>>> + VADC_CHAN(LR_MUX3_BUF_PU1_XO_THERM, 0) /* 0x7c */
>>> +
>>> + VADC_CHAN(LR_MUX1_PU2_BAT_THERM, 0) /* 0xb0 */
>>> + VADC_CHAN(LR_MUX2_PU2_BAT_ID, 0)
>>> + VADC_CHAN(LR_MUX3_PU2_XO_THERM, 0)
>>> + VADC_CHAN(LR_MUX4_PU2_AMUX_THM1, 0)
>>> + VADC_CHAN(LR_MUX5_PU2_AMUX_THM2, 0)
>>> + VADC_CHAN(LR_MUX6_PU2_AMUX_THM3, 0)
>>> + VADC_CHAN(LR_MUX7_PU2_AMUX_HW_ID, 0)
>>> + VADC_CHAN(LR_MUX8_PU2_AMUX_THM4, 0)
>>> + VADC_CHAN(LR_MUX9_PU2_AMUX_THM5, 0)
>>> + VADC_CHAN(LR_MUX10_PU2_AMUX_USB_ID, 0) /* 0xb9 */
>>> + VADC_CHAN(LR_MUX3_BUF_PU2_XO_THERM, 0) /* 0xbc */
>>> +
>>> + VADC_CHAN(LR_MUX1_PU1_PU2_BAT_THERM, 0) /* 0xf0 */
>>> + VADC_CHAN(LR_MUX2_PU1_PU2_BAT_ID, 0)
>>> + VADC_CHAN(LR_MUX3_PU1_PU2_XO_THERM, 0)
>>> + VADC_CHAN(LR_MUX4_PU1_PU2_AMUX_THM1, 0)
>>> + VADC_CHAN(LR_MUX5_PU1_PU2_AMUX_THM2, 0)
>>> + VADC_CHAN(LR_MUX6_PU1_PU2_AMUX_THM3, 0)
>>> + VADC_CHAN(LR_MUX7_PU1_PU2_AMUX_HW_ID, 0)
>>> + VADC_CHAN(LR_MUX8_PU1_PU2_AMUX_THM4, 0)
>>> + VADC_CHAN(LR_MUX9_PU1_PU2_AMUX_THM5, 0)
>>> + VADC_CHAN(LR_MUX10_PU1_PU2_AMUX_USB_ID, 0) /* 0xf9 */
>>> + VADC_CHAN(LR_MUX3_BUF_PU1_PU2_XO_THERM, 0) /* 0xfc */
>>> +};
>>> +
>>> +static int
>>> +vadc_get_dt_channel_data(struct vadc_priv *vadc, struct device_node *node)
>>> +{
>>> + struct vadc_channel *vchan;
>>> + u32 channel, value, varr[2];
>>> + int rc, pre, time, avg, decim;
>> Drop pre, time, avg and decim and reuse rc instead?
>>> + const char *name = node->name;
>>> +
>>> + rc = of_property_read_u32(node, "reg", &channel);
>>> + if (rc) {
>>> + dev_dbg(vadc->dev, "invalid channel number %s\n", name);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + if (channel >= vadc->nchannels) {
>>> + dev_dbg(vadc->dev, "%s invalid channel number %d\n", name,
>>> + channel);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + vchan = &vadc->channels[channel];
> Could you not have these in the same order as the iio_chan_spec array?
> Hence move the lookups that are scattered elsewhere in the driver to here?
>

<snip>

>>> +static int vadc_probe(struct platform_device *pdev)
>>> +{
>>> + struct device_node *node = pdev->dev.of_node;
>>> + struct device *dev = &pdev->dev;
>>> + struct iio_dev *indio_dev;
>>> + struct vadc_channel *vchan;
>>> + struct vadc_priv *vadc;
>>> + struct resource *res;
>>> + struct regmap *regmap;
>>> + int rc, irq_eoc, n;
>> unsigned int n?
>>> +
>>> + regmap = dev_get_regmap(dev->parent, NULL);
>>> + if (!regmap)
>>> + return -ENODEV;
>>> +
>>> + indio_dev = devm_iio_device_alloc(dev, sizeof(*vadc));
>>> + if (!indio_dev)
>>> + return -ENOMEM;
>>> +
>>> + vadc = iio_priv(indio_dev);
>>> + vadc->dev = dev;
>>> + vadc->regmap = regmap;
>>> + vadc->is_ref_measured = false;
>>> + init_completion(&vadc->complete);
>>> +
>>> + vadc->nchannels = ARRAY_SIZE(vadc_channels);
>>> + vadc->channels = devm_kcalloc(dev, vadc->nchannels,
>>> + sizeof(*vadc->channels), GFP_KERNEL);
> Interesting. This is always the same size as the vadc_channels so you
> might as well keep them in the same order and simplify various corners of
> the code.
>>> + if (!vadc->channels)
>>> + return -ENOMEM;
>>> +
> I wonder if we can't vadc->channels rather more different from vadc_channels?
> Perhaps vadc->channelspriv or similar? Confused me a little at first.
>>> + for (n = 0; n < vadc->nchannels; n++) {
>>> + vchan = &vadc->channels[n];
>>> + /* set default channel properties */
>>> + vchan->number = -1; /* inactive */
>>> + vchan->prescaling = vadc_channels[n].address;
>>> + vchan->decimation = VADC_DEF_DECIMATION;
>>> + vchan->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
>>> + vchan->avg_samples = VADC_DEF_AVG_SAMPLES;
>>> + vchan->calibration = VADC_DEF_CALIB_TYPE;
>>> + }
>>> +
>>> + platform_set_drvdata(pdev, vadc);
>>> +
>>> + res = platform_get_resource(pdev, IORESOURCE_REG, 0);
>>> + if (!res)
>>> + return -ENODEV;
>>> +
>>> + vadc->base = res->start;
>>> +
>>> + rc = vadc_version_check(vadc);
>>> + if (rc < 0)
>>> + return -ENODEV;
>>> +
>>> + rc = vadc_get_dt_data(vadc, node);
>>> + if (rc < 0)
>>> + return rc;
>>> +
> Do we need an explicit flag to indicate poll mode rather than just
> using the absense of the irq being specified to select that mode?

I will think about this. Maybe I will check the returned value from
platform_get_irq to see is the IRQ resource exist. If the IRQ doesn't
exist I will fallback to polling.

<snip>


--
regards,
Stan
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/