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