2019-05-27 08:55:06 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2014-08-08 13:12:17 +00:00
/*
* LTC2952 ( PowerPath ) driver
*
* Copyright ( C ) 2014 , Xsens Technologies BV < info @ xsens . com >
2018-08-23 17:01:26 -07:00
* Maintainer : René Moll < linux @ r - moll . nl >
2014-08-08 13:12:17 +00:00
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* - Description
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* This driver is to be used with an external PowerPath Controller ( LTC2952 ) .
* Its function is to determine when a external shut down is triggered
* and react by properly shutting down the system .
*
* This driver expects a device tree with a ltc2952 entry for pin mapping .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* - GPIO
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* The following GPIOs are used :
* - trigger ( input )
* A level change indicates the shut - down trigger . If it ' s state reverts
* within the time - out defined by trigger_delay , the shut down is not
2015-01-14 09:15:45 +01:00
* executed . If no pin is assigned to this input , the driver will start the
* watchdog toggle immediately . The chip will only power off the system if
* it is requested to do so through the kill line .
2014-08-08 13:12:17 +00:00
*
* - watchdog ( output )
* Once a shut down is triggered , the driver will toggle this signal ,
* with an internal ( wde_interval ) to stall the hardware shut down .
*
* - kill ( output )
* The last action during shut down is triggering this signalling , such
* that the PowerPath Control will power down the hardware .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* - Interrupts
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* The driver requires a non - shared , edge - triggered interrupt on the trigger
* GPIO .
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/device.h>
# include <linux/platform_device.h>
# include <linux/ktime.h>
# include <linux/slab.h>
# include <linux/kmod.h>
# include <linux/module.h>
2021-06-30 18:54:59 -07:00
# include <linux/panic_notifier.h>
2018-06-19 22:47:28 -07:00
# include <linux/mod_devicetable.h>
2014-08-08 13:12:17 +00:00
# include <linux/gpio/consumer.h>
# include <linux/reboot.h>
2021-02-03 22:49:00 +01:00
# include <linux/property.h>
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:38 +01:00
struct ltc2952_poweroff {
2014-08-08 13:12:17 +00:00
struct hrtimer timer_trigger ;
struct hrtimer timer_wde ;
ktime_t trigger_delay ;
ktime_t wde_interval ;
struct device * dev ;
2015-01-14 09:15:36 +01:00
struct gpio_desc * gpio_trigger ;
struct gpio_desc * gpio_watchdog ;
struct gpio_desc * gpio_kill ;
2015-01-14 09:15:39 +01:00
bool kernel_panic ;
struct notifier_block panic_notifier ;
2014-08-08 13:12:17 +00:00
} ;
2015-01-14 09:15:38 +01:00
# define to_ltc2952(p, m) container_of(p, struct ltc2952_poweroff, m)
/*
* This global variable is only needed for pm_power_off . We should
* remove it entirely once we don ' t need the global state anymore .
*/
static struct ltc2952_poweroff * ltc2952_data ;
2014-08-08 13:12:17 +00:00
/**
* ltc2952_poweroff_timer_wde - Timer callback
* Toggles the watchdog reset signal each wde_interval
*
* @ timer : corresponding timer
*
* Returns HRTIMER_RESTART for an infinite loop which will only stop when the
* machine actually shuts down
*/
static enum hrtimer_restart ltc2952_poweroff_timer_wde ( struct hrtimer * timer )
{
int state ;
2015-01-14 09:15:38 +01:00
struct ltc2952_poweroff * data = to_ltc2952 ( timer , timer_wde ) ;
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:39 +01:00
if ( data - > kernel_panic )
2014-08-08 13:12:17 +00:00
return HRTIMER_NORESTART ;
2015-01-14 09:15:38 +01:00
state = gpiod_get_value ( data - > gpio_watchdog ) ;
gpiod_set_value ( data - > gpio_watchdog , ! state ) ;
2014-08-08 13:12:17 +00:00
2021-09-23 18:04:28 +02:00
hrtimer_forward_now ( timer , data - > wde_interval ) ;
2014-08-08 13:12:17 +00:00
return HRTIMER_RESTART ;
}
2015-01-14 09:15:45 +01:00
static void ltc2952_poweroff_start_wde ( struct ltc2952_poweroff * data )
2014-08-08 13:12:17 +00:00
{
2015-04-13 16:43:35 +02:00
hrtimer_start ( & data - > timer_wde , data - > wde_interval , HRTIMER_MODE_REL ) ;
2015-01-14 09:15:45 +01:00
}
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:45 +01:00
static enum hrtimer_restart
ltc2952_poweroff_timer_trigger ( struct hrtimer * timer )
{
struct ltc2952_poweroff * data = to_ltc2952 ( timer , timer_trigger ) ;
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:45 +01:00
ltc2952_poweroff_start_wde ( data ) ;
dev_info ( data - > dev , " executing shutdown \n " ) ;
2014-08-08 13:12:17 +00:00
orderly_poweroff ( true ) ;
return HRTIMER_NORESTART ;
}
/**
* ltc2952_poweroff_handler - Interrupt handler
* Triggered each time the trigger signal changes state and ( de ) activates a
* time - out ( timer_trigger ) . Once the time - out is actually reached the shut
* down is executed .
*
* @ irq : IRQ number
* @ dev_id : pointer to the main data structure
*/
static irqreturn_t ltc2952_poweroff_handler ( int irq , void * dev_id )
{
2015-01-14 09:15:38 +01:00
struct ltc2952_poweroff * data = dev_id ;
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:41 +01:00
if ( data - > kernel_panic | | hrtimer_active ( & data - > timer_wde ) ) {
2014-08-08 13:12:17 +00:00
/* shutdown is already triggered, nothing to do any more */
2015-01-14 09:15:41 +01:00
return IRQ_HANDLED ;
2014-08-08 13:12:17 +00:00
}
2015-01-14 09:15:44 +01:00
if ( gpiod_get_value ( data - > gpio_trigger ) ) {
2015-04-13 16:43:35 +02:00
hrtimer_start ( & data - > timer_trigger , data - > trigger_delay ,
HRTIMER_MODE_REL ) ;
2014-08-08 13:12:17 +00:00
} else {
2015-01-14 09:15:41 +01:00
hrtimer_cancel ( & data - > timer_trigger ) ;
2014-08-08 13:12:17 +00:00
}
return IRQ_HANDLED ;
}
static void ltc2952_poweroff_kill ( void )
{
2015-01-14 09:15:36 +01:00
gpiod_set_value ( ltc2952_data - > gpio_kill , 1 ) ;
2014-08-08 13:12:17 +00:00
}
2015-01-14 09:15:38 +01:00
static void ltc2952_poweroff_default ( struct ltc2952_poweroff * data )
2014-08-08 13:12:17 +00:00
{
2021-11-05 08:20:50 -07:00
data - > wde_interval = 300L * NSEC_PER_MSEC ;
data - > trigger_delay = ktime_set ( 2 , 500L * NSEC_PER_MSEC ) ;
2014-08-08 13:12:17 +00:00
hrtimer_init ( & data - > timer_trigger , CLOCK_MONOTONIC , HRTIMER_MODE_REL ) ;
2015-01-14 09:15:42 +01:00
data - > timer_trigger . function = ltc2952_poweroff_timer_trigger ;
2014-08-08 13:12:17 +00:00
hrtimer_init ( & data - > timer_wde , CLOCK_MONOTONIC , HRTIMER_MODE_REL ) ;
2015-01-14 09:15:42 +01:00
data - > timer_wde . function = ltc2952_poweroff_timer_wde ;
2014-08-08 13:12:17 +00:00
}
static int ltc2952_poweroff_init ( struct platform_device * pdev )
{
2015-01-14 09:15:45 +01:00
int ret ;
2021-02-03 22:49:00 +01:00
u32 trigger_delay_ms ;
2015-01-14 09:15:38 +01:00
struct ltc2952_poweroff * data = platform_get_drvdata ( pdev ) ;
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:38 +01:00
ltc2952_poweroff_default ( data ) ;
2014-08-08 13:12:17 +00:00
2021-02-03 22:49:00 +01:00
if ( ! device_property_read_u32 ( & pdev - > dev , " trigger-delay-ms " ,
& trigger_delay_ms ) ) {
data - > trigger_delay = ktime_set ( trigger_delay_ms / MSEC_PER_SEC ,
( trigger_delay_ms % MSEC_PER_SEC ) * NSEC_PER_MSEC ) ;
}
2015-01-14 09:15:38 +01:00
data - > gpio_watchdog = devm_gpiod_get ( & pdev - > dev , " watchdog " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( data - > gpio_watchdog ) ) {
ret = PTR_ERR ( data - > gpio_watchdog ) ;
2015-01-14 09:15:36 +01:00
dev_err ( & pdev - > dev , " unable to claim gpio \" watchdog \" \n " ) ;
return ret ;
}
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:38 +01:00
data - > gpio_kill = devm_gpiod_get ( & pdev - > dev , " kill " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( data - > gpio_kill ) ) {
ret = PTR_ERR ( data - > gpio_kill ) ;
2015-01-14 09:15:36 +01:00
dev_err ( & pdev - > dev , " unable to claim gpio \" kill \" \n " ) ;
2015-01-14 09:15:37 +01:00
return ret ;
2015-01-14 09:15:36 +01:00
}
2015-05-18 22:45:07 +02:00
data - > gpio_trigger = devm_gpiod_get_optional ( & pdev - > dev , " trigger " ,
GPIOD_IN ) ;
2015-01-14 09:15:45 +01:00
if ( IS_ERR ( data - > gpio_trigger ) ) {
/*
* It ' s not a problem if the trigger gpio isn ' t available , but
* it is worth a warning if its use was defined in the device
* tree .
*/
2015-05-18 22:45:07 +02:00
dev_err ( & pdev - > dev , " unable to claim gpio \" trigger \" \n " ) ;
2015-01-14 09:15:45 +01:00
data - > gpio_trigger = NULL ;
2014-08-08 13:12:17 +00:00
}
2015-01-14 09:15:45 +01:00
if ( devm_request_irq ( & pdev - > dev , gpiod_to_irq ( data - > gpio_trigger ) ,
ltc2952_poweroff_handler ,
( IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING ) ,
" ltc2952-poweroff " ,
data ) ) {
/*
* Some things may have happened :
* - No trigger input was defined
* - Claiming the GPIO failed
* - We could not map to an IRQ
* - We couldn ' t register an interrupt handler
*
* None of these really are problems , but all of them
* disqualify the push button from controlling the power .
*
* It is therefore important to note that if the ltc2952
* detects a button press for long enough , it will still start
* its own powerdown window and cut the power on us if we don ' t
* start the watchdog trigger .
*/
if ( data - > gpio_trigger ) {
dev_warn ( & pdev - > dev ,
" unable to configure the trigger interrupt \n " ) ;
devm_gpiod_put ( & pdev - > dev , data - > gpio_trigger ) ;
data - > gpio_trigger = NULL ;
}
dev_info ( & pdev - > dev ,
" power down trigger input will not be used \n " ) ;
ltc2952_poweroff_start_wde ( data ) ;
2014-08-08 13:12:17 +00:00
}
return 0 ;
}
2015-01-14 09:15:39 +01:00
static int ltc2952_poweroff_notify_panic ( struct notifier_block * nb ,
unsigned long code , void * unused )
{
struct ltc2952_poweroff * data = to_ltc2952 ( nb , panic_notifier ) ;
data - > kernel_panic = true ;
return NOTIFY_DONE ;
}
2014-08-08 13:12:17 +00:00
static int ltc2952_poweroff_probe ( struct platform_device * pdev )
{
int ret ;
2015-01-14 09:15:38 +01:00
struct ltc2952_poweroff * data ;
2014-08-08 13:12:17 +00:00
if ( pm_power_off ) {
dev_err ( & pdev - > dev , " pm_power_off already registered " ) ;
return - EBUSY ;
}
2015-01-14 09:15:38 +01:00
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
2014-08-08 13:12:17 +00:00
return - ENOMEM ;
2015-01-14 09:15:38 +01:00
data - > dev = & pdev - > dev ;
platform_set_drvdata ( pdev , data ) ;
2014-08-08 13:12:17 +00:00
ret = ltc2952_poweroff_init ( pdev ) ;
if ( ret )
2015-01-14 09:15:34 +01:00
return ret ;
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:38 +01:00
/* TODO: remove ltc2952_data */
ltc2952_data = data ;
2015-01-14 09:15:42 +01:00
pm_power_off = ltc2952_poweroff_kill ;
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:39 +01:00
data - > panic_notifier . notifier_call = ltc2952_poweroff_notify_panic ;
atomic_notifier_chain_register ( & panic_notifier_list ,
& data - > panic_notifier ) ;
2014-08-08 13:12:17 +00:00
dev_info ( & pdev - > dev , " probe successful \n " ) ;
return 0 ;
}
static int ltc2952_poweroff_remove ( struct platform_device * pdev )
{
2015-01-14 09:15:39 +01:00
struct ltc2952_poweroff * data = platform_get_drvdata ( pdev ) ;
2014-08-08 13:12:17 +00:00
2015-01-14 09:15:39 +01:00
pm_power_off = NULL ;
2015-01-14 09:15:43 +01:00
hrtimer_cancel ( & data - > timer_trigger ) ;
hrtimer_cancel ( & data - > timer_wde ) ;
2015-01-14 09:15:39 +01:00
atomic_notifier_chain_unregister ( & panic_notifier_list ,
& data - > panic_notifier ) ;
2014-08-08 13:12:17 +00:00
return 0 ;
}
static const struct of_device_id of_ltc2952_poweroff_match [ ] = {
{ . compatible = " lltc,ltc2952 " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , of_ltc2952_poweroff_match ) ;
static struct platform_driver ltc2952_poweroff_driver = {
. probe = ltc2952_poweroff_probe ,
. remove = ltc2952_poweroff_remove ,
. driver = {
. name = " ltc2952-poweroff " ,
. of_match_table = of_ltc2952_poweroff_match ,
} ,
} ;
2015-01-14 09:15:39 +01:00
module_platform_driver ( ltc2952_poweroff_driver ) ;
2014-08-08 13:12:17 +00:00
2018-08-23 17:01:26 -07:00
MODULE_AUTHOR ( " René Moll <rene.moll@xsens.com> " ) ;
2014-08-08 13:12:17 +00:00
MODULE_DESCRIPTION ( " LTC PowerPath power-off driver " ) ;