Gestion des LED d'état sur Android 11 via GPIO

Introduction

Cette implémentation permet de contrôler deux LED d'état à l'aide de ports GPIO sur un dispositif fonctionnant sous Android 11. Par défaut, la LED bleue est activée au démarrage. Lors de la mise en veille par le bouton d'alimentation, la LED rouge s'allume, et au réveil, la LED bleue reprend son état actif.

Analyse technique

La tentative initiale d'intégrer le contrôle dans un pilote de rétroéclairage PWM a échoué car l'appareil ne dispose pas d'écran connecté via MIPI, eDP ou LVDS ; le signal vidéo est directement émis vers un écran HDMI depuis le processeur, rendant inaccessible le contrôle PWM. De même, l'intégration dans des pilotes d'écran gérés par I2C, comme le GM8775C, n'est pas viable. La solution retenue consiste donc à créer un nœud de fichier dans le pilote de bas niveau, exposant une interface sysfs pour permettre à la couche applicative de contrôler les GPIO.

Solution technique

A. Pilote de bas niveau

Un module noyau C, nommé led_gpio_driver.c, a été développé pour gérer les GPIO.


/*
 * Pilote pour LED contrôlées par GPIO.
 * Basé sur une conception open-source.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>

struct led_gpio_data {
    struct gpio_desc *active_pin;
    struct gpio_desc *standby_pin;
};

static const struct of_device_id led_gpio_ids[] = {
    { .compatible = "vendor,led-gpio", .data = NULL },
    {},
};
MODULE_DEVICE_TABLE(of, led_gpio_ids);

static ssize_t store_led_state(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    struct led_gpio_data *data = dev_get_drvdata(dev);
    int input_val = simple_strtol(buf, NULL, 10);

    if (input_val) {
        gpiod_direction_output(data->active_pin, 1);
        gpiod_direction_output(data->standby_pin, 0);
    } else {
        gpiod_direction_output(data->active_pin, 0);
        gpiod_direction_output(data->standby_pin, 1);
    }
    return count;
}

static ssize_t show_led_state(struct device *dev, struct device_attribute *attr, char *buf)
{
    return 0;
}

static DEVICE_ATTR(led_control, 0644, show_led_state, store_led_state);

static struct attribute *led_control_attrs[] = {
    &dev_attr_led_control.attr,
    NULL,
};

static struct attribute_group led_control_group = {
    .name = "led_control",
    .attrs = led_control_attrs,
};

static int led_gpio_probe(struct platform_device *pdev)
{
    struct led_gpio_data *drvdata;
    int ret;

    drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
    if (!drvdata)
        return -ENOMEM;

    platform_set_drvdata(pdev, drvdata);

    drvdata->active_pin = devm_gpiod_get_optional(&pdev->dev, "active", GPIOD_OUT_LOW);
    if (IS_ERR(drvdata->active_pin)) {
        ret = PTR_ERR(drvdata->active_pin);
        dev_err(&pdev->dev, "Erreur GPIO active: %d\n", ret);
        return ret;
    }

    drvdata->standby_pin = devm_gpiod_get_optional(&pdev->dev, "standby", GPIOD_OUT_LOW);
    if (IS_ERR(drvdata->standby_pin)) {
        ret = PTR_ERR(drvdata->standby_pin);
        dev_err(&pdev->dev, "Erreur GPIO standby: %d\n", ret);
        return ret;
    }

    // Initialisation: active high, standby low
    gpiod_direction_output(drvdata->active_pin, 1);
    gpiod_direction_output(drvdata->standby_pin, 0);

    ret = sysfs_create_group(&pdev->dev.kobj, &led_control_group);
    if (ret)
        dev_err(&pdev->dev, "Échec création groupe sysfs\n");

    return 0;
}

static int led_gpio_remove(struct platform_device *pdev)
{
    return 0;
}

#ifdef CONFIG_PM
static int led_gpio_suspend(struct device *dev)
{
    struct led_gpio_data *drvdata = dev_get_drvdata(dev);
    gpiod_direction_output(drvdata->active_pin, 0);
    return 0;
}

static int led_gpio_resume(struct device *dev)
{
    struct led_gpio_data *drvdata = dev_get_drvdata(dev);
    gpiod_direction_output(drvdata->active_pin, 1);
    return 0;
}

static const struct dev_pm_ops led_gpio_pm = {
    .suspend = led_gpio_suspend,
    .resume = led_gpio_resume,
};
#endif

static struct platform_driver led_gpio_plat_driver = {
    .probe = led_gpio_probe,
    .remove = led_gpio_remove,
    .driver = {
        .name = "led-gpio-ctl",
        .owner = THIS_MODULE,
        .of_match_table = led_gpio_ids,
#ifdef CONFIG_PM
        .pm = &led_gpio_pm,
#endif
    },
};

static int __init led_gpio_init(void)
{
    return platform_driver_register(&led_gpio_plat_driver);
}

static void __exit led_gpio_exit(void)
{
    platform_driver_unregister(&led_gpio_plat_driver);
}

module_init(led_gpio_init);
module_exit(led_gpio_exit);
MODULE_LICENSE("GPL");

B. Couche applicative

Le contrôle des LED est déclenché par les événements de veille et de réveil dans le service PowerManagerService d'Android. Les modifications suivantes ont été apportées.


--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -119,6 +119,14 @@
 import java.util.List;
 import java.util.Objects;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+
 /**
  * Service de gestion de puissance pour Android.
  */
@@ -1598,6 +1606,42 @@
         }
     }
 
+    private void updateLed(int mode) {
+        final String LED_FILE = "/sys/devices/platform/led-gpio-ctl/led_control/led_control";
+        String data = (mode == 1) ? "1" : "0";
+        File node = new File(LED_FILE);
+
+        if (!node.exists()) {
+            Log.e(TAG, "Fichier LED non trouvé");
+            return;
+        }
+
+        FileOutputStream fos = null;
+        BufferedWriter bw = null;
+        try {
+            fos = new FileOutputStream(node);
+            bw = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));
+            bw.write(data);
+            bw.flush();
+        } catch (IOException ex) {
+            Log.e(TAG, "Erreur écriture LED: " + ex.getMessage());
+        } finally {
+            if (bw != null) {
+                try { bw.close(); } catch (IOException ignored) {}
+            }
+        }
+    }
+
     private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
         if (DEBUG_SPEW) {
             Log.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime
@@ -1690,6 +1734,8 @@
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "wakeUp");
         try {
+            Log.i(TAG, "Activation LED pour mode actif");
+            updateLed(1);
             Log.i(TAG, "Waking up from "
                     + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked())
                     + " (uid=" + reasonUid
@@ -1748,6 +1794,8 @@
         try {
             reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
                     Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+            Log.i(TAG, "Désactivation LED pour mode veille");
+            updateLed(0);
             Log.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
                     + " (uid " + uid + ")...");

C. Validation

Après déploiement, l'appui sur le bouton d'alimentation déclenche l'allumage de la LED rouge lors de la mise en veille. Au réveil, la LED bleue s'active, confirmant le fonctionnement attendu.

Étiquettes: Android GPIO LED Linux Kernel

Publié le 28 juillet à 02h32