179 lines
4.3 KiB
C
179 lines
4.3 KiB
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 property_entry geode_restart_key_props[] = {
|
||
|
{ /* Placeholder for GPIO property */ },
|
||
|
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_key_props[0] = PROPERTY_ENTRY_GPIO("gpios",
|
||
|
&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 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 = kcalloc(n_leds, sizeof(*swnodes), GFP_KERNEL);
|
||
|
if (!swnodes)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
/*
|
||
|
* Each LED is represented by 3 properties: "gpios",
|
||
|
* "linux,default-trigger", and am empty terminator.
|
||
|
*/
|
||
|
props = kcalloc(n_leds * 3, sizeof(*props), GFP_KERNEL);
|
||
|
if (!props) {
|
||
|
err = -ENOMEM;
|
||
|
goto err_free_swnodes;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
props[i * 3 + 0] =
|
||
|
PROPERTY_ENTRY_GPIO("gpios", &geode_gpiochip_node,
|
||
|
leds[i].pin, GPIO_ACTIVE_LOW);
|
||
|
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(props);
|
||
|
err_free_swnodes:
|
||
|
kfree(swnodes);
|
||
|
return err;
|
||
|
}
|