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


hns

Well-Known Member
Joined
Dec 4, 2011
Messages
601
Location
Oberhaching
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...
 

Risca

Active Member
Joined
Sep 25, 2011
Messages
63
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*
 

Risca

Active Member
Joined
Sep 25, 2011
Messages
63
Rate my volume wheel :D
signal-2022-08-28-173729_002.jpeg
 

Risca

Active Member
Joined
Sep 25, 2011
Messages
63
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:
 

Risca

Active Member
Joined
Sep 25, 2011
Messages
63
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 :)
 

Confuzzled

Very Active Member
Joined
Oct 1, 2018
Messages
237
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.
 

Risca

Active Member
Joined
Sep 25, 2011
Messages
63
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.
 

pimaster

Very Active Member
Joined
Oct 19, 2015
Messages
110
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
 

Risca

Active Member
Joined
Sep 25, 2011
Messages
63
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
 
Top