2014-08-08 13:12:17 +00:00
/*
* LTC2952 ( PowerPath ) driver
*
* Copyright ( C ) 2014 , Xsens Technologies BV < info @ xsens . com >
* Maintainer : Ren <EFBFBD> Moll < linux @ r - moll . nl >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version 2
* of the License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* - 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
* executed .
*
* - 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>
# include <linux/gpio/consumer.h>
# include <linux/reboot.h>
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 )
{
ktime_t now ;
int state ;
unsigned long overruns ;
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
now = hrtimer_cb_get_time ( timer ) ;
2015-01-14 09:15:38 +01:00
overruns = hrtimer_forward ( timer , now , data - > wde_interval ) ;
2014-08-08 13:12:17 +00:00
return HRTIMER_RESTART ;
}
2015-01-14 09:15:38 +01:00
static enum hrtimer_restart
ltc2952_poweroff_timer_trigger ( struct hrtimer * timer )
2014-08-08 13:12:17 +00:00
{
2015-01-14 09:15:38 +01:00
struct ltc2952_poweroff * data = to_ltc2952 ( timer , timer_trigger ) ;
int ret = hrtimer_start ( & data - > timer_wde ,
data - > wde_interval , HRTIMER_MODE_REL ) ;
2014-08-08 13:12:17 +00:00
if ( ret ) {
2015-01-14 09:15:38 +01:00
dev_err ( data - > dev , " unable to start the timer \n " ) ;
2014-08-08 13:12:17 +00:00
/*
* The device will not toggle the watchdog reset ,
* thus shut down is only safe if the PowerPath controller
* has a long enough time - off before triggering a hardware
* power - off .
*
* Only sending a warning as the system will power - off anyway
*/
}
2015-01-14 09:15:38 +01:00
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
}
if ( ! hrtimer_active ( & data - > timer_trigger ) ) {
2015-01-14 09:15:41 +01:00
if ( hrtimer_start ( & data - > timer_trigger , data - > trigger_delay ,
HRTIMER_MODE_REL ) )
2014-08-08 13:12:17 +00:00
dev_err ( data - > dev , " unable to start the wait timer \n " ) ;
} else {
2015-01-14 09:15:41 +01:00
hrtimer_cancel ( & data - > timer_trigger ) ;
2014-08-08 13:12:17 +00:00
/* omitting return value check, timer should have been valid */
}
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
{
data - > wde_interval = ktime_set ( 0 , 300L * 1E6L ) ;
data - > trigger_delay = ktime_set ( 2 , 500L * 1E6L ) ;
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 )
{
int ret , virq ;
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
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-01-14 09:15:38 +01:00
data - > gpio_trigger = devm_gpiod_get ( & pdev - > dev , " trigger " ,
GPIOD_IN ) ;
2015-01-14 09:15:36 +01:00
if ( IS_ERR ( ltc2952_data - > gpio_trigger ) ) {
ret = PTR_ERR ( ltc2952_data - > gpio_trigger ) ;
dev_err ( & pdev - > dev , " unable to claim gpio \" trigger \" \n " ) ;
2015-01-14 09:15:37 +01:00
return ret ;
2014-08-08 13:12:17 +00:00
}
2015-01-14 09:15:38 +01:00
virq = gpiod_to_irq ( data - > gpio_trigger ) ;
2014-08-08 13:12:17 +00:00
if ( virq < 0 ) {
dev_err ( & pdev - > dev , " cannot map GPIO as interrupt " ) ;
2015-01-14 09:15:37 +01:00
return ret ;
2014-08-08 13:12:17 +00:00
}
2015-01-14 09:15:35 +01:00
ret = devm_request_irq ( & pdev - > dev , virq ,
ltc2952_poweroff_handler ,
( IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING ) ,
" ltc2952-poweroff " ,
2015-01-14 09:15:38 +01:00
data ) ;
2014-08-08 13:12:17 +00:00
if ( ret ) {
dev_err ( & pdev - > dev , " cannot configure an interrupt handler \n " ) ;
2015-01-14 09:15:37 +01:00
return ret ;
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 ;
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
MODULE_AUTHOR ( " Ren<EFBFBD> Moll <rene.moll@xsens.com> " ) ;
MODULE_DESCRIPTION ( " LTC PowerPath power-off driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;