Thread (15 messages) 15 messages, 5 authors, 2020-09-10

Re: [PATCH v3 1/2] iio: adc: mt6360: Add ADC driver for MT6360

From: Jonathan Cameron <Jonathan.Cameron@Huawei.com>
Date: 2020-09-08 17:05:34
Also in: linux-iio, linux-mediatek, lkml

...
quoted
quoted
quoted
quoted
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MT6360_REG_PMUCHGCTRL3       0x313
+#define MT6360_REG_PMUADCCFG 0x356
+#define MT6360_REG_PMUADCRPT1        0x35A
+
+/* PMUCHGCTRL3 0x313 */
+#define MT6360_AICR_MASK     0xFC
+#define MT6360_AICR_SHFT     2
+#define MT6360_AICR_400MA    0x6
+/* PMUADCCFG 0x356 */
+#define MT6360_ADCEN_MASK    0x8000
+/* PMUADCRPT1 0x35A */
+#define MT6360_PREFERCH_MASK 0xF0
+#define MT6360_PREFERCH_SHFT 4
+#define MT6360_RPTCH_MASK    0x0F
+
+enum {
+     MT6360_CHAN_USBID = 0,
+     MT6360_CHAN_VBUSDIV5,
+     MT6360_CHAN_VBUSDIV2,
+     MT6360_CHAN_VSYS,
+     MT6360_CHAN_VBAT,
+     MT6360_CHAN_IBUS,
+     MT6360_CHAN_IBAT,
+     MT6360_CHAN_CHG_VDDP,
+     MT6360_CHAN_TEMP_JC,
+     MT6360_CHAN_VREF_TS,
+     MT6360_CHAN_TS,
+     MT6360_CHAN_MAX,
+};
+
+struct mt6360_adc_data {
+     struct device *dev;
+     struct regmap *regmap;
+     struct completion adc_complete;
+     struct mutex adc_lock;
+     ktime_t last_off_timestamps[MT6360_CHAN_MAX];
+     int irq;
+};
+
+static inline int mt6360_adc_val_converter(int val, int multiplier, int offset, int divisor)
+{
+     return ((val * multiplier) + offset) / divisor;  
Why could we not report these values to userspace or consumer drivers and let
them deal with the conversion if they actually needed it?
Mapping this to

(val + new_offset) * multiplier would be a little messy, but not too bad.

The advantage would be that we would then be providing the data needed
to get real units for values read from the buffers without having to
do all the maths in kernel (without access to floating point).

 
As above, if I use formula "(val + new_offset) * multiplier",
the junction temperature channel multiplier will be floating point
1.05, i don't know how to express.  
As Andy mentioned, we do this all over the place.
IIO_VAL_INT_PLUS_MICRO

The key is that we want to push the burden of doing this maths to the user
not the source.  
ACK.
Can I keep IIO_CHAN_INFO_PROCESSED function be reserved for user in
kernel space?
No. We have utility functions that will apply the multiplier as needed so
there is no significant advantage in doing this and it won't be consistent
with the majority of other drivers. 
quoted
Often what is actually of interest is whether a temperature passed a threshold.
In that case, you can transform the threshold into the units of the ADC (so the
reverse directly to you would do with processed data) and only have to do the
maths once per change of the threshold instead of for every sample.

There are helper functions to do the maths for you, should you actually
need SI units.
 
ACK
quoted
quoted
 
quoted
quoted
+}
+
+static int mt6360_adc_convert_processed_val(struct mt6360_adc_data *info, int chan_idx, int *val)
+{
+     unsigned int regval = 0;
+     const struct converter {
+             int multiplier;
+             int offset;
+             int divisor;
+     } adc_converter[MT6360_CHAN_MAX] = {
+             { 1250, 0, 1}, /* USBID */
+             { 6250, 0, 1}, /* VBUSDIV5 */
+             { 2500, 0, 1}, /* VBUSDIV2 */
+             { 1250, 0, 1}, /* VSYS */
+             { 1250, 0, 1}, /* VBAT */
+             { 2500, 0, 1}, /* IBUS */
+             { 2500, 0, 1}, /* IBAT */
+             { 1250, 0, 1}, /* CHG_VDDP */
+             { 105, -8000, 100}, /* TEMP_JC */
+             { 1250, 0, 1}, /* VREF_TS */
+             { 1250, 0, 1}, /* TS */
+     }, sp_ibus_adc_converter = { 1900, 0, 1 }, *sel_converter;
+     int ret;
+
+     sel_converter = adc_converter + chan_idx;
+     if (chan_idx == MT6360_CHAN_IBUS) {
+             /* ibus chan will be affected by aicr config */
+             /* if aicr < 400, apply the special ibus converter */
+             ret = regmap_read(info->regmap, MT6360_REG_PMUCHGCTRL3, &regval);
+             if (ret)
+                     return ret;
+
+             regval = (regval & MT6360_AICR_MASK) >> MT6360_AICR_SHFT;
+             if (regval < MT6360_AICR_400MA)
+                     sel_converter = &sp_ibus_adc_converter;
+     }
+
+     *val = mt6360_adc_val_converter(*val, sel_converter->multiplier, sel_converter->offset,
+                                     sel_converter->divisor);
+
+     return 0;
+}
+
+static int mt6360_adc_read_processed(struct mt6360_adc_data *mad, int channel, int *val)
+{
+     u16 adc_enable;
+     u8 rpt[3];
+     ktime_t start_t, predict_end_t;
+     long timeout;
+     int value, ret;
+
+     mutex_lock(&mad->adc_lock);
+
+     /* select preferred channel that we want */
+     ret = regmap_update_bits(mad->regmap, MT6360_REG_PMUADCRPT1, MT6360_PREFERCH_MASK,
+                              channel << MT6360_PREFERCH_SHFT);
+     if (ret)
+             goto out_adc;
+
+     /* enable adc channel we want and adc_en */
+     adc_enable = MT6360_ADCEN_MASK | BIT(channel);
+     adc_enable = cpu_to_be16(adc_enable);  
Use a local be16 to store that. It will make it a little clearer
that we are doing something 'unusual' here.  Perhaps a comment on
why this odd code exists would also help?
 
ACK
 
quoted
quoted
+     ret = regmap_raw_write(mad->regmap, MT6360_REG_PMUADCCFG, (void *)&adc_enable, sizeof(u16));
+     if (ret)
+             goto out_adc;
+
+     start_t = ktime_get();
+     predict_end_t = ktime_add_ms(mad->last_off_timestamps[channel], 50);
+
+     if (ktime_after(start_t, predict_end_t))
+             predict_end_t = ktime_add_ms(start_t, 25);
+     else
+             predict_end_t = ktime_add_ms(start_t, 75);
+
+     enable_irq(mad->irq);
+adc_retry:
+     reinit_completion(&mad->adc_complete);
+
+     /* wait for conversion to complete */
+     timeout = wait_for_completion_timeout(&mad->adc_complete, msecs_to_jiffies(200));
+     if (timeout == 0) {
+             ret = -ETIMEDOUT;
+             goto out_adc_conv;
+     } else if (timeout < 0) {
+             ret = -EINTR;
+             goto out_adc_conv;
+     }
+
+     ret = regmap_raw_read(mad->regmap, MT6360_REG_PMUADCRPT1, rpt, sizeof(rpt));
+     if (ret)
+             goto out_adc_conv;
+
+     /* check the current reported channel */
+     if ((rpt[0] & MT6360_RPTCH_MASK) != channel) {
+             dev_dbg(mad->dev, "not wanted channel report [%02x]\n", rpt[0]);  
This and the one below feel like error messages rather than debug ones.
 
We have two function "battery zero current voltage(ZCV)" and "TypeC
OTP" will auto run ADC at background.
ZCV_EN will run VBAT_ADC when TA plug in, TypeC OTP will run TS_ADC
when TypeC attach.
We need to check report channel for ADC report data match is our desire channel.  
So there is firmware messing with it underneath?  Oh goody.
Add a comment explaining this.
 
ACK, I try to write a comment as below

        /*
         * There are two functions, ZCV and TypeC OTP, running ADC
VBAT and TS in background,
         * and ADC samples are taken on a fixed frequency no matter
read the previous one or not.
         * To avoid conflict need set minimum time threshold after
enable ADC and check report
         * channel is the same.
         * The worst case is run the same ADC twice and background
function is also running,
         * ADC conversion sequence is desire channel before start ADC,
background ADC, desire
         * channel after start ADC. So the minimum correct data is
three times of typical
         * conversion time.
         */
Looks good.
quoted
quoted
 
quoted
quoted
+             goto adc_retry;
+     }
+
+     if (!ktime_after(ktime_get(), predict_end_t)) {
+             dev_dbg(mad->dev, "time is not after one adc_conv_t\n");  
Does this actually happen? If feels like we are being a bit over protective
here.  I'd definitely like to see a comment saying why this protection
might be needed.
 
When ADC_EN and MT6360_CHANx_EN is enable, the channel x will keep
running again and again
I supposed to get immediate data which is generated after I start it.  
Just to check my understanding.

This is an edge triggered interrupt and it triggers every time a new sample
is taken.  Those samples are taken on a fixed frequency irrespective of whether
we have read the previous one?
 
Yes.
I use LEVEL_LOW trigger in latest review MFD patch.
I'm not sure I follow that comment.  How can you do that if it's a repeating
edge trigger? 
quoted
quoted
When I disable ADC_CHANx_EN, the H/W logical ADC is still running.
If I run the same ADC immediately, I may get the old result about this channel.
MT6360 ADC typical conversation time is about 25ms.
So We need ignore which irq trigger below 25ms.  
Normal trick for this sort of case is to just not use the interrupt.
Just read after 25+delta msecs and you are guaranteed to get the right answer.

 
ACK, I will try to use polling
Is the pseudocode correct?

mdelay(predict_end_t);
while (true) {
    read adc event is occured
    check report channel is the same
    if the same, read report ADC data and break while loop
    else msleep(per ADC conversion time)
}
Looks correct to me.  We should 'know' the event has happened but
still need to check the channel is the expected one.

...


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help