2016-07-06 16:27:26 +03:00
/*
* Copyright ( c ) 2016 , Fuzhou Rockchip Electronics Co . , Ltd
*
* 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 .
*/
# include <linux/device.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/reboot.h>
2017-05-27 09:51:29 +03:00
# include <linux/reboot-mode.h>
2016-07-06 16:27:26 +03:00
# define PREFIX "mode-"
struct mode_info {
const char * mode ;
u32 magic ;
struct list_head list ;
} ;
static unsigned int get_reboot_mode_magic ( struct reboot_mode_driver * reboot ,
const char * cmd )
{
const char * normal = " normal " ;
int magic = 0 ;
struct mode_info * info ;
if ( ! cmd )
cmd = normal ;
list_for_each_entry ( info , & reboot - > head , list ) {
if ( ! strcmp ( info - > mode , cmd ) ) {
magic = info - > magic ;
break ;
}
}
return magic ;
}
static int reboot_mode_notify ( struct notifier_block * this ,
unsigned long mode , void * cmd )
{
struct reboot_mode_driver * reboot ;
unsigned int magic ;
reboot = container_of ( this , struct reboot_mode_driver , reboot_notifier ) ;
magic = get_reboot_mode_magic ( reboot , cmd ) ;
if ( magic )
reboot - > write ( reboot , magic ) ;
return NOTIFY_DONE ;
}
/**
* reboot_mode_register - register a reboot mode driver
* @ reboot : reboot mode driver
*
* Returns : 0 on success or a negative error code on failure .
*/
int reboot_mode_register ( struct reboot_mode_driver * reboot )
{
struct mode_info * info ;
struct property * prop ;
struct device_node * np = reboot - > dev - > of_node ;
size_t len = strlen ( PREFIX ) ;
int ret ;
INIT_LIST_HEAD ( & reboot - > head ) ;
for_each_property_of_node ( np , prop ) {
if ( strncmp ( prop - > name , PREFIX , len ) )
continue ;
info = devm_kzalloc ( reboot - > dev , sizeof ( * info ) , GFP_KERNEL ) ;
if ( ! info ) {
ret = - ENOMEM ;
goto error ;
}
if ( of_property_read_u32 ( np , prop - > name , & info - > magic ) ) {
dev_err ( reboot - > dev , " reboot mode %s without magic number \n " ,
info - > mode ) ;
devm_kfree ( reboot - > dev , info ) ;
continue ;
}
info - > mode = kstrdup_const ( prop - > name + len , GFP_KERNEL ) ;
if ( ! info - > mode ) {
ret = - ENOMEM ;
goto error ;
} else if ( info - > mode [ 0 ] = = ' \0 ' ) {
kfree_const ( info - > mode ) ;
ret = - EINVAL ;
dev_err ( reboot - > dev , " invalid mode name(%s): too short! \n " ,
prop - > name ) ;
goto error ;
}
list_add_tail ( & info - > list , & reboot - > head ) ;
}
reboot - > reboot_notifier . notifier_call = reboot_mode_notify ;
register_reboot_notifier ( & reboot - > reboot_notifier ) ;
return 0 ;
error :
list_for_each_entry ( info , & reboot - > head , list )
kfree_const ( info - > mode ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( reboot_mode_register ) ;
/**
* reboot_mode_unregister - unregister a reboot mode driver
* @ reboot : reboot mode driver
*/
int reboot_mode_unregister ( struct reboot_mode_driver * reboot )
{
struct mode_info * info ;
unregister_reboot_notifier ( & reboot - > reboot_notifier ) ;
list_for_each_entry ( info , & reboot - > head , list )
kfree_const ( info - > mode ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( reboot_mode_unregister ) ;
2016-08-04 08:04:05 +03:00
static void devm_reboot_mode_release ( struct device * dev , void * res )
{
reboot_mode_unregister ( * ( struct reboot_mode_driver * * ) res ) ;
}
/**
* devm_reboot_mode_register ( ) - resource managed reboot_mode_register ( )
* @ dev : device to associate this resource with
* @ reboot : reboot mode driver
*
* Returns : 0 on success or a negative error code on failure .
*/
int devm_reboot_mode_register ( struct device * dev ,
struct reboot_mode_driver * reboot )
{
struct reboot_mode_driver * * dr ;
int rc ;
dr = devres_alloc ( devm_reboot_mode_release , sizeof ( * dr ) , GFP_KERNEL ) ;
if ( ! dr )
return - ENOMEM ;
rc = reboot_mode_register ( reboot ) ;
if ( rc ) {
devres_free ( dr ) ;
return rc ;
}
* dr = reboot ;
devres_add ( dev , dr ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( devm_reboot_mode_register ) ;
static int devm_reboot_mode_match ( struct device * dev , void * res , void * data )
{
struct reboot_mode_driver * * p = res ;
if ( WARN_ON ( ! p | | ! * p ) )
return 0 ;
return * p = = data ;
}
/**
* devm_reboot_mode_unregister ( ) - resource managed reboot_mode_unregister ( )
* @ dev : device to associate this resource with
* @ reboot : reboot mode driver
*/
void devm_reboot_mode_unregister ( struct device * dev ,
struct reboot_mode_driver * reboot )
{
WARN_ON ( devres_release ( dev ,
devm_reboot_mode_release ,
devm_reboot_mode_match , reboot ) ) ;
}
EXPORT_SYMBOL_GPL ( devm_reboot_mode_unregister ) ;
2016-07-06 16:27:26 +03:00
MODULE_AUTHOR ( " Andy Yan <andy.yan@rock-chips.com " ) ;
MODULE_DESCRIPTION ( " System reboot mode core library " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;