Couldn't wait for my Pyra, so I bought a OMAP5432 devboard


I have no idea where GPADC2 is routed on my board
GPADC_IN2 (audio accessory/general purpose) seems to be connected to GND throut a 0Ω resistor (R82)...

GPADC_IN7 is called "VCC_SENSE". You probably could also try channel 10 which is VBUS and play with USB voltages...
 
GPADC_IN2 (audio accessory/general purpose) seems to be connected to GND throut a 0Ω resistor (R82)...
0Ω, you say? We'll see about that! *starts looking for my soldering iron*
 
Rate my volume wheel :D
signal-2022-08-28-173729_002.jpeg
 
Eureka! I can get interrupts from TWL6037 on both high and low threshold! No more polling needed :)
Code:
# insmod /lib/modules/5.10.120/kernel/drivers/iio/adc/palmas_gpadc.ko
[ 1585.028884] palmas-gpadc 48070000.i2c:palmas@48:gpadc: palmas_adc_wakeup_configure
# [ 1592.146455] palmas-gpadc 48070000.i2c:palmas@48:gpadc: Threshold interrupt 161 occurs
[ 1592.154346] palmas-gpadc 48070000.i2c:palmas@48:gpadc: palmas_disable_auto_conversion
...
[ 1592.459033] palmas-gpadc 48070000.i2c:palmas@48:gpadc: Threshold interrupt 161 occurs
[ 1592.466898] palmas-gpadc 48070000.i2c:palmas@48:gpadc: palmas_disable_auto_conversion
#
#
# [ 1602.021610] palmas-gpadc 48070000.i2c:palmas@48:gpadc: Threshold interrupt 162 occurs
[ 1602.029683] palmas-gpadc 48070000.i2c:palmas@48:gpadc: palmas_disable_auto_conversion
...
[ 1602.333821] palmas-gpadc 48070000.i2c:palmas@48:gpadc: Threshold interrupt 162 occurs
[ 1602.341710] palmas-gpadc 48070000.i2c:palmas@48:gpadc: palmas_disable_auto_conversion
This is my debugging output when first turning my volume wheel above the threshold, back into the deadzone, then below the threshold.
A bunch of code changes are still needed, but the basic hardware support is there! :cool:
 
Okay, got most of the boilerplate code done to set high and low threshold values from userspace and integrate with the IIO Event subsystem. In theory, this should be enough to create a userspace application that will get woken up only when the "volume" (ADC value) changes. The TWL6037 will monitor the value for us and trigger an interrupt if it's either below or above certain threshold. Userspace will get woken up, read the current value, change sound volume, set new thresholds, and then go back to waiting for the next interrupt.

Still got some bugs left to fix, some code cleanup, and possibly removing some old functionality from the palmas_gpadc driver. We'll see if I have time to work on this next week :)
 
Okay, got most of the boilerplate code done to set high and low threshold values from userspace and integrate with the IIO Event subsystem. In theory, this should be enough to create a userspace application that will get woken up only when the "volume" (ADC value) changes. The TWL6037 will monitor the value for us and trigger an interrupt if it's either below or above certain threshold. Userspace will get woken up, read the current value, change sound volume, set new thresholds, and then go back to waiting for the next interrupt.

Still got some bugs left to fix, some code cleanup, and possibly removing some old functionality from the palmas_gpadc driver. We'll see if I have time to work on this next week :)
Excellent work! Thank you.

I hope the TWL6037 approach decreases the power consumption, but even if it doesn't, it's still a great job.
 
Okay, finally got some time to work on this (on a train!) and I think I got the driver code fairly stable now. I managed to rip off the solder pad for R82, so I had to start using GPADC_IN1 which is routed to R80 right next to R82.

I'll fix up the code in proper git, email friendly patches eventually (I know @hns has been asking for it), but in the meantime I'll just dump my current work here:
Diff:
--- a/drivers/iio/adc/palmas_gpadc.c      2022-07-07 15:21:08.000000000 +0200
+++ b/drivers/iio/adc/palmas_gpadc.c      2022-09-18 22:51:51.547587391 +0200
@@ -20,6 +20,7 @@
 #include <linux/completion.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/iio/events.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/machine.h>
 #include <linux/iio/driver.h>
@@ -76,6 +77,11 @@
        PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
 };
 
+struct palmas_gpadc_thresholds {
+       int high_thresh;
+       int low_thresh;
+};
+
 /*
  * struct palmas_gpadc - the palmas_gpadc structure
  * @ch0_current:       channel 0 current source setting
@@ -110,8 +116,27 @@
        bool                            wakeup1_enable;
        bool                            wakeup2_enable;
        int                             auto_conversion_period;
+       struct palmas_adc_wakeup_property event0;
+       struct palmas_adc_wakeup_property event1;
+       struct palmas_gpadc_thresholds  thresh_data[PALMAS_ADC_CH_MAX];
 };
 
+static struct palmas_adc_wakeup_property *palmas_gpadc_get_event_channel(
+       struct palmas_gpadc *adc, int adc_chan, enum iio_event_direction dir)
+{
+       if (adc_chan == adc->event0.adc_channel_number &&
+           ((dir == IIO_EV_DIR_RISING && adc->event0.adc_high_threshold) ||
+            (dir == IIO_EV_DIR_FALLING && adc->event0.adc_low_threshold)))
+               return &adc->event0;
+
+       if (adc_chan == adc->event1.adc_channel_number &&
+           ((dir == IIO_EV_DIR_RISING && adc->event1.adc_high_threshold) ||
+            (dir == IIO_EV_DIR_FALLING && adc->event1.adc_low_threshold)))
+               return &adc->event1;
+
+       return NULL;
+}
+
 /*
  * GPADC lock issue in AUTO mode.
  * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
@@ -181,11 +206,25 @@
 
 static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
 {
-       struct palmas_gpadc *adc = data;
+       struct iio_dev *indio_dev = data;
+       struct palmas_gpadc *adc = iio_priv(indio_dev);
+       struct palmas_adc_wakeup_property *ev;
 
-       dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq);
+       dev_info(adc->dev, "Threshold interrupt %d occurs\n", irq);
        palmas_disable_auto_conversion(adc);
 
+       ev = (irq == adc->irq_auto_0) ? &adc->event0 : &adc->event1;
+       if (ev->adc_channel_number != -1) {
+               enum iio_event_direction dir;
+               u64 code;
+
+               dir = ev->adc_high_threshold ?
+                       IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
+               code = IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, ev->adc_channel_number,
+                                           IIO_EV_TYPE_THRESH, dir);
+               iio_push_event(indio_dev, code, iio_get_time_ns(indio_dev));
+       }
+
        return IRQ_HANDLED;
 }
 
@@ -273,6 +312,10 @@
 {
        int ret;
 
+       if (palmas_gpadc_get_event_channel(adc, adc_chan, IIO_EV_DIR_RISING) ||
+           palmas_gpadc_get_event_channel(adc, adc_chan, IIO_EV_DIR_FALLING))
+               return 0; // ADC in freerunning mode
+
        ret = palmas_gpadc_enable(adc, adc_chan, true);
        if (ret < 0)
                return ret;
@@ -331,29 +374,49 @@
 {
        unsigned int val;
        int ret;
+       const bool freerunning =
+               palmas_gpadc_get_event_channel(adc, adc_chan, IIO_EV_DIR_RISING) ||
+               palmas_gpadc_get_event_channel(adc, adc_chan, IIO_EV_DIR_FALLING);
+
+       if (freerunning) {
+               const unsigned int reg =
+                       (adc_chan == adc->event0.adc_channel_number) ?
+                               PALMAS_GPADC_AUTO_CONV0_LSB :
+                               PALMAS_GPADC_AUTO_CONV1_LSB;
 
-       init_completion(&adc->conv_completion);
-       ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
-                               PALMAS_GPADC_SW_SELECT,
-                               PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
-                               PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
-       if (ret < 0) {
-               dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret);
-               return ret;
+               ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
+                                       reg, &val, 2);
+               if (ret < 0) {
+                       dev_err(adc->dev, "AUTO_CONV%x_LSB read failed: %d\n",
+                               reg == PALMAS_GPADC_AUTO_CONV0_LSB ? 0 : 1,
+                               ret);
+                       return ret;
+               }
        }
+       else {
+               init_completion(&adc->conv_completion);
+               ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+                                       PALMAS_GPADC_SW_SELECT,
+                                       PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
+                                       PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
+               if (ret < 0) {
+                       dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret);
+                       return ret;
+               }
 
-       ret = wait_for_completion_timeout(&adc->conv_completion,
-                               PALMAS_ADC_CONVERSION_TIMEOUT);
-       if (ret == 0) {
-               dev_err(adc->dev, "conversion not completed\n");
-               return -ETIMEDOUT;
-       }
+               ret = wait_for_completion_timeout(&adc->conv_completion,
+                                       PALMAS_ADC_CONVERSION_TIMEOUT);
+               if (ret == 0) {
+                       dev_err(adc->dev, "conversion not completed\n");
+                       return -ETIMEDOUT;
+               }
 
-       ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
-                               PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
-       if (ret < 0) {
-               dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret);
-               return ret;
+               ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
+                                       PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
+               if (ret < 0) {
+                       dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret);
+                       return ret;
+               }
        }
 
        ret = val & 0xFFF;
@@ -375,6 +438,70 @@
 
        val = (val * adc->adc_info[adc_chan].gain) / 1000;
 
+       dev_info(adc->dev, "calib %d offset %d gain_error %d gain %d\n",
+                adc->adc_info[adc_chan].is_uncalibrated,
+                adc->adc_info[adc_chan].offset,
+                adc->adc_info[adc_chan].gain_error,
+                adc->adc_info[adc_chan].gain);
+
+       return val;
+}
+
+static int palmas_gpadc_get_high_threshold(struct palmas_gpadc *adc,
+                                          int adc_chan, int val)
+{
+       const int INL = 2;
+       int gain_drift;
+       int offset_drift;
+
+       val = (val * 1000) / adc->adc_info[adc_chan].gain;
+
+       if (!adc->adc_info[adc_chan].is_uncalibrated) {
+               val = (val * adc->adc_info[adc_chan].gain_error +
+                      adc->adc_info[adc_chan].offset) /
+                       1000;
+               gain_drift = 1002;
+               offset_drift = 2;
+       }
+       else {
+               gain_drift = 1022;
+               offset_drift = 36;
+       }
+
+       // add tolerance to threshold
+       val = ((val + INL) * gain_drift) / 1000 + offset_drift;
+
+       dev_notice(adc->dev, "high thresh: %d\n", val);
+
+       return val;
+}
+
+static int palmas_gpadc_get_low_threshold(struct palmas_gpadc *adc,
+                                         int adc_chan, int val)
+{
+       const int INL = 2;
+       int gain_drift;
+       int offset_drift;
+
+       val = (val * 1000) / adc->adc_info[adc_chan].gain;
+
+        if (!adc->adc_info[adc_chan].is_uncalibrated) {
+            val = (val * adc->adc_info[adc_chan].gain_error -
+                  adc->adc_info[adc_chan].offset) /
+                   1000;
+            gain_drift = 998;
+            offset_drift = 2;
+        }
+        else {
+            gain_drift = 978;
+            offset_drift = 36;
+        }
+
+       // calculate tolerances
+       val = ((val - INL) * gain_drift) / 1000 - offset_drift;
+
+       dev_notice(adc->dev, "low thresh: %d\n", val);
+
        return val;
 }
 
@@ -424,8 +551,218 @@
        return ret;
 }
 
+static int palmas_gpadc_read_event_config(struct iio_dev *indio_dev,
+       const struct iio_chan_spec *chan, enum iio_event_type type,
+       enum iio_event_direction dir)
+{
+       struct palmas_gpadc *adc = iio_priv(indio_dev);
+       int adc_chan = chan->channel;
+       int ret = 0;
+
+       if (adc_chan > PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH)
+               return -EINVAL;
+
+       mutex_lock(&indio_dev->mlock);
+
+       if (palmas_gpadc_get_event_channel(adc, adc_chan, dir)) {
+               ret = 1;
+       }
+
+       mutex_unlock(&indio_dev->mlock);
+
+       return ret;
+}
+
+static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc);
+static int palmas_gpadc_reconfigure_event_channels(struct palmas_gpadc *adc)
+{
+       adc->wakeup1_data = adc->event0;
+       adc->wakeup1_enable = adc->event0.adc_channel_number == -1 ?
+               false : true;
+       adc->wakeup2_data = adc->event1;
+       adc->wakeup2_enable = adc->event1.adc_channel_number == -1 ?
+               false : true;
+
+       if (adc->event0.adc_channel_number == -1 &&
+           adc->event1.adc_channel_number == -1)
+               return palmas_disable_auto_conversion(adc);
+       return palmas_adc_wakeup_configure(adc);
+}
+
+static int palmas_gpadc_enable_event_config(struct palmas_gpadc *adc,
+       const struct iio_chan_spec *chan, enum iio_event_direction dir)
+{
+       struct palmas_adc_wakeup_property *ev;
+       int adc_chan = chan->channel;
+
+       if (palmas_gpadc_get_event_channel(adc, adc_chan, dir))
+               /* already enabled */
+               return 0;
+
+       if (adc->event0.adc_channel_number == -1)
+               ev = &adc->event0;
+       else if (adc->event1.adc_channel_number == -1) {
+               /* event0 has to be the lowest channel */
+               if (adc_chan < adc->event0.adc_channel_number) {
+                       adc->event1 = adc->event0;
+                       ev = &adc->event0;
+               }
+               else
+                       ev = &adc->event1;
+       }
+       else /* both AUTO channels already in use */ {
+               dev_warn(adc->dev, "event0 - %d, event1 - %d\n",
+                        adc->event0.adc_channel_number,
+                        adc->event1.adc_channel_number);
+               return -EBUSY;
+       }
+
+       ev->adc_channel_number = adc_chan;
+       if (dir == IIO_EV_DIR_RISING)
+               ev->adc_high_threshold = adc->thresh_data[adc_chan].high_thresh;
+       else
+               ev->adc_low_threshold = adc->thresh_data[adc_chan].low_thresh;
+
+       return palmas_gpadc_reconfigure_event_channels(adc);
+}
+
+static int palmas_gpadc_disable_event_config(struct palmas_gpadc *adc,
+       const struct iio_chan_spec *chan, enum iio_event_direction dir)
+{
+       int adc_chan = chan->channel;
+       struct palmas_adc_wakeup_property *ev =
+               palmas_gpadc_get_event_channel(adc, adc_chan, dir);
+
+       if (!ev)
+               return 0;
+
+       if (ev == &adc->event0) {
+               adc->event0 = adc->event1;
+               ev = &adc->event1;
+       }
+
+       ev->adc_channel_number = -1;
+       ev->adc_high_threshold = 0;
+       ev->adc_low_threshold = 0;
+
+       return palmas_gpadc_reconfigure_event_channels(adc);
+}
+
+static int palmas_gpadc_write_event_config(struct iio_dev *indio_dev,
+       const struct iio_chan_spec *chan, enum iio_event_type type,
+       enum iio_event_direction dir, int state)
+{
+       struct palmas_gpadc *adc = iio_priv(indio_dev);
+       int adc_chan = chan->channel;
+       int ret = 0;
+
+       if (adc_chan > PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH)
+               return -EINVAL;
+
+       mutex_lock(&indio_dev->mlock);
+
+       if (state)
+               ret = palmas_gpadc_enable_event_config(adc, chan, dir);
+       else
+               ret = palmas_gpadc_disable_event_config(adc, chan, dir);
+
+       mutex_unlock(&indio_dev->mlock);
+
+       return ret;
+}
+
+static int palmas_gpadc_read_event_value(struct iio_dev *indio_dev,
+       const struct iio_chan_spec *chan, enum iio_event_type type,
+       enum iio_event_direction dir, enum iio_event_info info, int *val,
+       int *val2)
+{
+       struct palmas_gpadc *adc = iio_priv(indio_dev);
+       int adc_chan = chan->channel;
+       int ret = 0;
+
+       if (adc_chan > PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH)
+               return -EINVAL;
+
+       mutex_lock(&indio_dev->mlock);
+
+       switch (info) {
+       case IIO_EV_INFO_VALUE:
+               *val = (dir == IIO_EV_DIR_RISING) ?
+                       adc->thresh_data[adc_chan].high_thresh :
+                       adc->thresh_data[adc_chan].low_thresh;
+               ret = IIO_VAL_INT;
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       mutex_unlock(&indio_dev->mlock);
+
+       return ret;
+}
+
+static int palmas_gpadc_write_event_value(struct iio_dev *indio_dev,
+       const struct iio_chan_spec *chan, enum iio_event_type type,
+       enum iio_event_direction dir, enum iio_event_info info, int val,
+       int val2)
+{
+       struct palmas_gpadc *adc = iio_priv(indio_dev);
+       int adc_chan = chan->channel;
+       int ret = 0;
+
+       if (adc_chan > PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH)
+               return -EINVAL;
+
+       mutex_lock(&indio_dev->mlock);
+       switch (info) {
+       case IIO_EV_INFO_VALUE:
+               if (val < 0 || val > 0xFFF) {
+                       ret = -EINVAL;
+                       break;
+               }
+               if (dir == IIO_EV_DIR_RISING)
+                       adc->thresh_data[adc_chan].high_thresh =
+                               palmas_gpadc_get_high_threshold(
+                                       adc, adc_chan, val);
+               else
+                       adc->thresh_data[adc_chan].low_thresh =
+                               palmas_gpadc_get_low_threshold(
+                                       adc, adc_chan, val);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       if (palmas_gpadc_get_event_channel(adc, adc_chan, dir))
+               ret = palmas_gpadc_reconfigure_event_channels(adc);
+
+       mutex_unlock(&indio_dev->mlock);
+
+       return ret;
+}
+
 static const struct iio_info palmas_gpadc_iio_info = {
        .read_raw = palmas_gpadc_read_raw,
+       .read_event_config = palmas_gpadc_read_event_config,
+       .write_event_config = palmas_gpadc_write_event_config,
+       .read_event_value = palmas_gpadc_read_event_value,
+       .write_event_value = palmas_gpadc_write_event_value,
+};
+
+static const struct iio_event_spec palmas_gpadc_events[] = {
+       {
+               .type = IIO_EV_TYPE_THRESH,
+               .dir = IIO_EV_DIR_RISING,
+               .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+                               BIT(IIO_EV_INFO_ENABLE),
+       }, {
+               .type = IIO_EV_TYPE_THRESH,
+               .dir = IIO_EV_DIR_FALLING,
+               .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+                               BIT(IIO_EV_INFO_ENABLE),
+       },
 };
 
 #define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info)    \
@@ -436,11 +773,13 @@
                        BIT(chan_info),                 \
        .indexed = 1,                                   \
        .channel = PALMAS_ADC_CH_##chan,                \
+       .event_spec = palmas_gpadc_events,              \
+       .num_event_specs = ARRAY_SIZE(palmas_gpadc_events)      \
 }
 
 static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
        PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
-       PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW),
+       PALMAS_ADC_CHAN_IIO(IN1, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
        PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
        PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW),
        PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
@@ -527,7 +866,8 @@
                ret = adc->irq;
                goto out;
        }
-       ret = request_threaded_irq(adc->irq, NULL,
+       ret = devm_request_threaded_irq(&pdev->dev,
+               adc->irq, NULL,
                palmas_gpadc_irq,
                IRQF_ONESHOT, dev_name(adc->dev),
                adc);
@@ -537,38 +877,45 @@
                goto out;
        }
 
+       adc->irq_auto_0 = platform_get_irq(pdev, 1);
+       ret = devm_request_threaded_irq(&pdev->dev,
+                       adc->irq_auto_0, NULL,
+                       palmas_gpadc_irq_auto,
+                       IRQF_ONESHOT,
+                       "palmas-adc-auto-0", indio_dev);
+       if (ret < 0) {
+               dev_err(adc->dev, "request auto0 irq %d failed: %d\n",
+                       adc->irq_auto_0, ret);
+               goto out;
+       }
+
+       adc->irq_auto_1 = platform_get_irq(pdev, 2);
+       ret = devm_request_threaded_irq(&pdev->dev,
+                       adc->irq_auto_1, NULL,
+                       palmas_gpadc_irq_auto,
+                       IRQF_ONESHOT,
+                       "palmas-adc-auto-1", indio_dev);
+       if (ret < 0) {
+               dev_err(adc->dev, "request auto1 irq %d failed: %d\n",
+                       adc->irq_auto_1, ret);
+               goto out;
+       }
+
        if (gpadc_pdata->adc_wakeup1_data) {
                memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data,
                        sizeof(adc->wakeup1_data));
                adc->wakeup1_enable = true;
-               adc->irq_auto_0 =  platform_get_irq(pdev, 1);
-               ret = request_threaded_irq(adc->irq_auto_0, NULL,
-                               palmas_gpadc_irq_auto,
-                               IRQF_ONESHOT,
-                               "palmas-adc-auto-0", adc);
-               if (ret < 0) {
-                       dev_err(adc->dev, "request auto0 irq %d failed: %d\n",
-                               adc->irq_auto_0, ret);
-                       goto out_irq_free;
-               }
        }
 
        if (gpadc_pdata->adc_wakeup2_data) {
                memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data,
                                sizeof(adc->wakeup2_data));
                adc->wakeup2_enable = true;
-               adc->irq_auto_1 =  platform_get_irq(pdev, 2);
-               ret = request_threaded_irq(adc->irq_auto_1, NULL,
-                               palmas_gpadc_irq_auto,
-                               IRQF_ONESHOT,
-                               "palmas-adc-auto-1", adc);
-               if (ret < 0) {
-                       dev_err(adc->dev, "request auto1 irq %d failed: %d\n",
-                               adc->irq_auto_1, ret);
-                       goto out_irq_auto0_free;
-               }
        }
 
+       adc->event0.adc_channel_number = -1;
+       adc->event1.adc_channel_number = -1;
+
        /* set the current source 0 (value 0/5/15/20 uA => 0..3) */
        if (gpadc_pdata->ch0_current <= 1)
                adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
@@ -600,7 +947,7 @@
        ret = iio_device_register(indio_dev);
        if (ret < 0) {
                dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
-               goto out_irq_auto1_free;
+               goto out;
        }
 
        device_set_wakeup_capable(&pdev->dev, 1);
@@ -614,31 +961,18 @@
 
        return 0;
 
-out_irq_auto1_free:
-       if (gpadc_pdata->adc_wakeup2_data)
-               free_irq(adc->irq_auto_1, adc);
-out_irq_auto0_free:
-       if (gpadc_pdata->adc_wakeup1_data)
-               free_irq(adc->irq_auto_0, adc);
-out_irq_free:
-       free_irq(adc->irq, adc);
 out:
        return ret;
 }
 
 static int palmas_gpadc_remove(struct platform_device *pdev)
 {
-       struct iio_dev *indio_dev = dev_to_iio_dev(&pdev->dev);
+       struct iio_dev *indio_dev = dev_get_drvdata(&pdev->dev);
        struct palmas_gpadc *adc = iio_priv(indio_dev);
 
        if (adc->wakeup1_enable || adc->wakeup2_enable)
                device_wakeup_disable(&pdev->dev);
        iio_device_unregister(indio_dev);
-       free_irq(adc->irq, adc);
-       if (adc->wakeup1_enable)
-               free_irq(adc->irq_auto_0, adc);
-       if (adc->wakeup2_enable)
-               free_irq(adc->irq_auto_1, adc);
 
        return 0;
 }
It has some extra debug printouts that I'll strip away when released, but this is starting to feel rather usable. It allows user space to set thresholds for low and high limits. It expects the same kind of value which you would get from reading /sys/bus/iio/devices/iio:device0/in_voltage1_input and calculates a suitable "raw" threshold value. I also added the recommended tolerances from the TI document named Guide to using the GPADC in TPS65903x, TPS65917-Q1, TPS65919-Q1, and TPS65916 Devices, which all use the same driver as the TWL6037. I changed the values in the guide to values I found in the data manual for TWL6037. Preferably, these are values that should come from device tree, but I couldn't be bothered :oops: maybe if I will try to upstream these changes later.

This is how you configure the values from userspace:
Code:
# echo 1000 > /sys/bus/iio/devices/iio:device0/events/in_voltage2_thresh_rising_value
# echo 200 > /sys/bus/iio/devices/iio:device0/events/in_voltage2_thresh_falling_value
# echo 1 > /sys/bus/iio/devices/iio:device0/events/in_voltage2_thresh_rising_en
# echo 1 > /sys/bus/iio/devices/iio:device0/events/in_voltage2_thresh_falling_en
Now the CPU will get interrupts whenever the input value reaches above 1000 or below 200, and it will send IIO events to userspace. New threshold values can be written directly to in_voltage2_thresh_rising_value and in_voltage2_thresh_falling_value without having to disable the threshold in between.

There is an example program bundled with the Linux kernel sources to listen for IIO events: https://git.kernel.org/pub/scm/linu.../linux.git/tree/tools/iio/iio_event_monitor.c
My plan is to write a program, based on these example sources, that reacts to the events that get sent out when the volume wheel goes past the low and high threshold; reads the current wheel value; and then sets new apropriate thresholds. It should of course also be able to set the volume, which is the whole point :D Maybe it would make sense to have the program call a (configurable) external program when setting the volume? Then you can adjust it based on what sound server (pulseaudio, pipewire, alsa) is being used.
 
Owner of Devboard and Pyra.
Wanted to test your code on a Pyra to give you some happy feedback.
I am unable to unload the existing palmas_gpadc kernel module before installing a custom one. The process hangs and the only thing I can do is reboot. After installing this module on the Devboard, this happens on both devboard and pyra.
Happens with both rmmod and rmmod -f
Are you checking changes while the system is running or are you "installing" (copying the compiled ko to /lib/modules/`kern`/) your changes to replace existing behaviour?
I'm a little behind on the devboard running 4.19 but my pyra is more up to date on 5.6
 
I run a custom OS based on Buildroot and it doesn't autoload the palmas_gpadc.ko. I usually overwrite the existing module and then do rmmod + insmod.

What does lsmod show? Who is using the palmas_gpadc module?

Oh, now I remember! The original kernel module has a bug in palmas_gpadc_remove() so it can't be removed without kernel crash! Check the patch I posted above for a fix. It's the very first line. I've sent this fix to @hns already so should be part of the next Letux kernel release
 
Finally some updates!

I got a simple monitor userspace program working now, and it works "okay" :) I still see some bugs from the kernel driver (I think!), and the program is quite limited. The kernel driver occasionally gets stuck and cannot change threshold values. I haven't figured out yet what's causing that, but I have some ideas. The only way to recover is to unload the kernel module and load it again. Also, writing and reading the threshold values produce different values! :mad:

My program takes 2 arguments:
Code:
# /root/pyra_vol_mon
Usage: /root/pyra_vol_mon <channel> <executable>
  • ADC channel to monitor (2 for the Pyra)
  • executable to be called whenever the voltage, i.e. the volume wheel, changes. The first argument to the executable is the current value as a string
I've attached a pre-compiled kernel module and monitor program for any brave soul to test!

Instructions how to test:
Code:
mv /lib/modules/$(uname -r)/kernel/drivers/iio/adc/palmas_gpadc.ko{,.bak}
cp palmas_gpadc.ko /lib/modules/$(uname -r)/kernel/drivers/iio/adc/palmas_gpadc.ko
reboot
# once rebooted
./pyra_vol_mon 2 /bin/echo

WARNING: This is very experimental code and may cause your Pyra to not boot! Only proceed if you know how to recover.

The code for the monitor program can be found here: https://github.com/Risca/pyra_vol_mon
 

Attachments

  • pyra_vol_mon.zip
    16.7 KB · Views: 217
Last edited:
Small update today. Fixed the bug where reading the threshold value would give you a different value as when you write the threshold :) Kernel patches attached
Nice. I'll integrate into letux-6.1-rc2. If you agree I can also include the code for your user-space tool.

It was just a little difficult to find out that it only merges cleanly with linux v5.9.
 
I'm based on git://git.ti.com/ti-linux-kernel/ti-linux-kernel.git, tag 08.04.00.004 (Linux-5.10.120).

You mean the patches don't apply clean on letux-6.1-rc2?
No, because they replace patches which are already included in letux-6.1-rc2. So the original ones were in the way.
And the patch system uses line numbers to locate where to apply patch fragments ("hunks") to. If these are too far away it will simply give up.
But I was able to write a script that finds out where they can cleanly be applied.
Then, git cherry-pick is clever enough to handle such situations :)
Ok, will do! Will go to "Letux/root/pyra_vol_mon" in the source code tree.
 
No, because they replace patches which are already included in letux-6.1-rc2. So the original ones were in the way.
And the patch system uses line numbers to locate where to apply patch fragments ("hunks") to. If these are too far away it will simply give up.
But I was able to write a script that finds out where they can cleanly be applied.
Then, git cherry-pick is clever enough to handle such situations :)
Ah, I see. I'll stop squashing my patches :oops: I just didn't feel right with providing a patch that introduced a bug
 
Ah, I see. I'll stop squashing my patches :oops: I just didn't feel right with providing a patch that introduced a bug
The best solution would be if you can push to some github repo and open new branches for each version of something. The I can "git remote add" that and git can process things through "git cherry-pick" much better than applying .patch files. And it can do some 3-way difference calculations and other magic :)

Anyways, I have added the pyra_vol_mon to letux-6.2-rc2 and can compile and run it directly on the Pyra through
Code:
cd pyra_vol_mon
CROSS_COMPILE="" make
./pyra_vol_monitor 0 echo
This seems to start but shows no sign of activity. I wasn't able to analyse deeper since the kernel didn't boot properly (tons of "drivers/bus/omap_l3_noc.c:139 l3_interrupt_handler" errors I have to understand first).
 
The best solution would be if you can push to some github repo and open new branches for each version of something. The I can "git remote add" that and git can process things through "git cherry-pick" much better than applying .patch files. And it can do some 3-way difference calculations and other magic :)

Anyways, I have added the pyra_vol_mon to letux-6.2-rc2 and can compile and run it directly on the Pyra through
Code:
cd pyra_vol_mon
CROSS_COMPILE="" make
./pyra_vol_monitor 0 echo
This seems to start but shows no sign of activity. I wasn't able to analyse deeper since the kernel didn't boot properly (tons of "drivers/bus/omap_l3_noc.c:139 l3_interrupt_handler" errors I have to understand first).
I think that should be
Code:
./pyra_vol_monitor 2 echo
for the Pyra.

I've forked the Linux repo on GitHub and pushed my working branch: https://github.com/Risca/linux/tree/palmas_gpadc
The very last commit is only meant for my devboard though, since it changes the IN1 channel from temp sensor to voltage. Probably doesn't do any harm on the Pyra *shrug*
 
Back
Top