| // 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/mfd/cs5535.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/slab.h> | |
| #include "geode-common.h" | |
| 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_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(&cs5535_gpio_swnode, | |
| 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(&cs5535_gpio_swnode, | |
| 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; | |
| } | |
| MODULE_IMPORT_NS("CS5535"); |