13d0407baSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
23d0407baSopenharmony_ci/*
33d0407baSopenharmony_ci * Toggles a GPIO pin to power down a device
43d0407baSopenharmony_ci *
53d0407baSopenharmony_ci * Jamie Lentin <jm@lentin.co.uk>
63d0407baSopenharmony_ci * Andrew Lunn <andrew@lunn.ch>
73d0407baSopenharmony_ci *
83d0407baSopenharmony_ci * Copyright (C) 2012 Jamie Lentin
93d0407baSopenharmony_ci */
103d0407baSopenharmony_ci#include <linux/kernel.h>
113d0407baSopenharmony_ci#include <linux/init.h>
123d0407baSopenharmony_ci#include <linux/delay.h>
133d0407baSopenharmony_ci#include <linux/platform_device.h>
143d0407baSopenharmony_ci#include <linux/gpio/consumer.h>
153d0407baSopenharmony_ci#include <linux/of_platform.h>
163d0407baSopenharmony_ci#include <linux/module.h>
173d0407baSopenharmony_ci
183d0407baSopenharmony_ci#define DEFAULT_TIMEOUT_MS 3000
193d0407baSopenharmony_ci/*
203d0407baSopenharmony_ci * Hold configuration here, cannot be more than one instance of the driver
213d0407baSopenharmony_ci * since pm_power_off itself is global.
223d0407baSopenharmony_ci */
233d0407baSopenharmony_cistatic struct gpio_desc *reset_gpio;
243d0407baSopenharmony_cistatic u32 timeout = DEFAULT_TIMEOUT_MS;
253d0407baSopenharmony_cistatic u32 active_delay = 100;
263d0407baSopenharmony_cistatic u32 inactive_delay = 100;
273d0407baSopenharmony_ci
283d0407baSopenharmony_cistatic void gpio_poweroff_do_poweroff(void)
293d0407baSopenharmony_ci{
303d0407baSopenharmony_ci    BUG_ON(!reset_gpio);
313d0407baSopenharmony_ci
323d0407baSopenharmony_ci    /* drive it active, also inactive->active edge */
333d0407baSopenharmony_ci    gpiod_direction_output(reset_gpio, 1);
343d0407baSopenharmony_ci    mdelay(active_delay);
353d0407baSopenharmony_ci
363d0407baSopenharmony_ci    /* drive inactive, also active->inactive edge */
373d0407baSopenharmony_ci    gpiod_set_value_cansleep(reset_gpio, 0);
383d0407baSopenharmony_ci    mdelay(inactive_delay);
393d0407baSopenharmony_ci
403d0407baSopenharmony_ci    /* drive it active, also inactive->active edge */
413d0407baSopenharmony_ci    gpiod_set_value_cansleep(reset_gpio, 1);
423d0407baSopenharmony_ci
433d0407baSopenharmony_ci    /* give it some time */
443d0407baSopenharmony_ci    mdelay(timeout);
453d0407baSopenharmony_ci
463d0407baSopenharmony_ci    WARN_ON(1);
473d0407baSopenharmony_ci}
483d0407baSopenharmony_ci
493d0407baSopenharmony_cistatic int gpio_poweroff_probe(struct platform_device *pdev)
503d0407baSopenharmony_ci{
513d0407baSopenharmony_ci    bool input = false;
523d0407baSopenharmony_ci    enum gpiod_flags flags;
533d0407baSopenharmony_ci
543d0407baSopenharmony_ci    /* If a pm_power_off function has already been added, leave it alone */
553d0407baSopenharmony_ci    if (pm_power_off != NULL) {
563d0407baSopenharmony_ci        dev_err(&pdev->dev, "%s: pm_power_off function already registered\n", __func__);
573d0407baSopenharmony_ci        return -EBUSY;
583d0407baSopenharmony_ci    }
593d0407baSopenharmony_ci
603d0407baSopenharmony_ci    input = device_property_read_bool(&pdev->dev, "input");
613d0407baSopenharmony_ci    if (input) {
623d0407baSopenharmony_ci        flags = GPIOD_IN;
633d0407baSopenharmony_ci    } else {
643d0407baSopenharmony_ci        flags = GPIOD_OUT_LOW;
653d0407baSopenharmony_ci    }
663d0407baSopenharmony_ci
673d0407baSopenharmony_ci    device_property_read_u32(&pdev->dev, "active-delay-ms", &active_delay);
683d0407baSopenharmony_ci    device_property_read_u32(&pdev->dev, "inactive-delay-ms", &inactive_delay);
693d0407baSopenharmony_ci    device_property_read_u32(&pdev->dev, "timeout-ms", &timeout);
703d0407baSopenharmony_ci
713d0407baSopenharmony_ci    reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags);
723d0407baSopenharmony_ci    if (IS_ERR(reset_gpio)) {
733d0407baSopenharmony_ci        return PTR_ERR(reset_gpio);
743d0407baSopenharmony_ci    }
753d0407baSopenharmony_ci
763d0407baSopenharmony_ci    pm_power_off = &gpio_poweroff_do_poweroff;
773d0407baSopenharmony_ci    return 0;
783d0407baSopenharmony_ci}
793d0407baSopenharmony_ci
803d0407baSopenharmony_cistatic int gpio_poweroff_remove(struct platform_device *pdev)
813d0407baSopenharmony_ci{
823d0407baSopenharmony_ci    if (pm_power_off == &gpio_poweroff_do_poweroff) {
833d0407baSopenharmony_ci        pm_power_off = NULL;
843d0407baSopenharmony_ci    }
853d0407baSopenharmony_ci
863d0407baSopenharmony_ci    return 0;
873d0407baSopenharmony_ci}
883d0407baSopenharmony_ci
893d0407baSopenharmony_cistatic const struct of_device_id of_gpio_poweroff_match[] = {
903d0407baSopenharmony_ci    {
913d0407baSopenharmony_ci        .compatible = "gpio-poweroff",
923d0407baSopenharmony_ci    },
933d0407baSopenharmony_ci    {},
943d0407baSopenharmony_ci};
953d0407baSopenharmony_ci
963d0407baSopenharmony_cistatic struct platform_driver gpio_poweroff_driver = {
973d0407baSopenharmony_ci    .probe = gpio_poweroff_probe,
983d0407baSopenharmony_ci    .remove = gpio_poweroff_remove,
993d0407baSopenharmony_ci    .driver =
1003d0407baSopenharmony_ci        {
1013d0407baSopenharmony_ci            .name = "poweroff-gpio",
1023d0407baSopenharmony_ci            .of_match_table = of_gpio_poweroff_match,
1033d0407baSopenharmony_ci        },
1043d0407baSopenharmony_ci};
1053d0407baSopenharmony_ci
1063d0407baSopenharmony_cimodule_platform_driver(gpio_poweroff_driver);
1073d0407baSopenharmony_ci
1083d0407baSopenharmony_ciMODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>");
1093d0407baSopenharmony_ciMODULE_DESCRIPTION("GPIO poweroff driver");
1103d0407baSopenharmony_ciMODULE_LICENSE("GPL v2");
1113d0407baSopenharmony_ciMODULE_ALIAS("platform:poweroff-gpio");
112