root/arch/x86/platform/geode/geode-common.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Shared helpers to register GPIO-connected buttons and LEDs
 * on AMD Geode boards.
 */

#include <linux/err.h>
#include <linux/gpio/machine.h>
#include <linux/gpio/property.h>
#include <linux/input.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

#include "geode-common.h"

static const struct software_node geode_gpiochip_node = {
        .name = "cs5535-gpio",
};

static const struct property_entry geode_gpio_keys_props[] = {
        PROPERTY_ENTRY_U32("poll-interval", 20),
        { }
};

static const struct software_node geode_gpio_keys_node = {
        .name = "geode-gpio-keys",
        .properties = geode_gpio_keys_props,
};

static struct software_node_ref_args geode_restart_gpio_ref;

static const struct property_entry geode_restart_key_props[] = {
        PROPERTY_ENTRY_REF_ARRAY_LEN("gpios", &geode_restart_gpio_ref, 1),
        PROPERTY_ENTRY_U32("linux,code", KEY_RESTART),
        PROPERTY_ENTRY_STRING("label", "Reset button"),
        PROPERTY_ENTRY_U32("debounce-interval", 100),
        { }
};

static const struct software_node geode_restart_key_node = {
        .parent = &geode_gpio_keys_node,
        .properties = geode_restart_key_props,
};

static const struct software_node *geode_gpio_keys_swnodes[] __initconst = {
        &geode_gpiochip_node,
        &geode_gpio_keys_node,
        &geode_restart_key_node,
        NULL
};

/*
 * Creates gpio-keys-polled device for the restart key.
 *
 * Note that it needs to be called first, before geode_create_leds(),
 * because it registers gpiochip software node used by both gpio-keys and
 * leds-gpio devices.
 */
int __init geode_create_restart_key(unsigned int pin)
{
        struct platform_device_info keys_info = {
                .name   = "gpio-keys-polled",
                .id     = 1,
        };
        struct platform_device *pd;
        int err;

        geode_restart_gpio_ref = SOFTWARE_NODE_REFERENCE(&geode_gpiochip_node,
                                                         pin, GPIO_ACTIVE_LOW);

        err = software_node_register_node_group(geode_gpio_keys_swnodes);
        if (err) {
                pr_err("failed to register gpio-keys software nodes: %d\n", err);
                return err;
        }

        keys_info.fwnode = software_node_fwnode(&geode_gpio_keys_node);

        pd = platform_device_register_full(&keys_info);
        err = PTR_ERR_OR_ZERO(pd);
        if (err) {
                pr_err("failed to create gpio-keys device: %d\n", err);
                software_node_unregister_node_group(geode_gpio_keys_swnodes);
                return err;
        }

        return 0;
}

static const struct software_node geode_gpio_leds_node = {
        .name = "geode-leds",
};

#define MAX_LEDS        3

int __init geode_create_leds(const char *label, const struct geode_led *leds,
                              unsigned int n_leds)
{
        const struct software_node *group[MAX_LEDS + 2] = { 0 };
        struct software_node *swnodes;
        struct property_entry *props;
        struct software_node_ref_args *gpio_refs;
        struct platform_device_info led_info = {
                .name   = "leds-gpio",
                .id     = PLATFORM_DEVID_NONE,
        };
        struct platform_device *led_dev;
        const char *node_name;
        int err;
        int i;

        if (n_leds > MAX_LEDS) {
                pr_err("%s: too many LEDs\n", __func__);
                return -EINVAL;
        }

        swnodes = kzalloc_objs(*swnodes, n_leds);
        if (!swnodes)
                return -ENOMEM;

        /*
         * Each LED is represented by 3 properties: "gpios",
         * "linux,default-trigger", and am empty terminator.
         */
        props = kzalloc_objs(*props, n_leds * 3);
        if (!props) {
                err = -ENOMEM;
                goto err_free_swnodes;
        }

        gpio_refs = kzalloc_objs(*gpio_refs, n_leds);
        if (!gpio_refs) {
                err = -ENOMEM;
                goto err_free_props;
        }

        group[0] = &geode_gpio_leds_node;
        for (i = 0; i < n_leds; i++) {
                node_name = kasprintf(GFP_KERNEL, "%s:%d", label, i);
                if (!node_name) {
                        err = -ENOMEM;
                        goto err_free_names;
                }

                gpio_refs[i] = SOFTWARE_NODE_REFERENCE(&geode_gpiochip_node,
                                                       leds[i].pin,
                                                       GPIO_ACTIVE_LOW);
                props[i * 3 + 0] =
                        PROPERTY_ENTRY_REF_ARRAY_LEN("gpios", &gpio_refs[i], 1);
                props[i * 3 + 1] =
                        PROPERTY_ENTRY_STRING("linux,default-trigger",
                                              leds[i].default_on ?
                                              "default-on" : "default-off");
                /* props[i * 3 + 2] is an empty terminator */

                swnodes[i] = SOFTWARE_NODE(node_name, &props[i * 3],
                                           &geode_gpio_leds_node);
                group[i + 1] = &swnodes[i];
        }

        err = software_node_register_node_group(group);
        if (err) {
                pr_err("failed to register LED software nodes: %d\n", err);
                goto err_free_names;
        }

        led_info.fwnode = software_node_fwnode(&geode_gpio_leds_node);

        led_dev = platform_device_register_full(&led_info);
        err = PTR_ERR_OR_ZERO(led_dev);
        if (err) {
                pr_err("failed to create LED device: %d\n", err);
                goto err_unregister_group;
        }

        return 0;

err_unregister_group:
        software_node_unregister_node_group(group);
err_free_names:
        while (--i >= 0)
                kfree(swnodes[i].name);
        kfree(gpio_refs);
err_free_props:
        kfree(props);
err_free_swnodes:
        kfree(swnodes);
        return err;
}