18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/device.h> 78c2ecf20Sopenharmony_ci#include <linux/init.h> 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/of.h> 118c2ecf20Sopenharmony_ci#include <linux/reboot.h> 128c2ecf20Sopenharmony_ci#include <linux/reboot-mode.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#define PREFIX "mode-" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistruct mode_info { 178c2ecf20Sopenharmony_ci const char *mode; 188c2ecf20Sopenharmony_ci u32 magic; 198c2ecf20Sopenharmony_ci struct list_head list; 208c2ecf20Sopenharmony_ci}; 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot, 238c2ecf20Sopenharmony_ci const char *cmd) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci const char *normal = "normal"; 268c2ecf20Sopenharmony_ci int magic = 0; 278c2ecf20Sopenharmony_ci struct mode_info *info; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci if (!cmd) 308c2ecf20Sopenharmony_ci cmd = normal; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci list_for_each_entry(info, &reboot->head, list) { 338c2ecf20Sopenharmony_ci if (!strcmp(info->mode, cmd)) { 348c2ecf20Sopenharmony_ci magic = info->magic; 358c2ecf20Sopenharmony_ci break; 368c2ecf20Sopenharmony_ci } 378c2ecf20Sopenharmony_ci } 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci return magic; 408c2ecf20Sopenharmony_ci} 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic int reboot_mode_notify(struct notifier_block *this, 438c2ecf20Sopenharmony_ci unsigned long mode, void *cmd) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct reboot_mode_driver *reboot; 468c2ecf20Sopenharmony_ci unsigned int magic; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci reboot = container_of(this, struct reboot_mode_driver, reboot_notifier); 498c2ecf20Sopenharmony_ci magic = get_reboot_mode_magic(reboot, cmd); 508c2ecf20Sopenharmony_ci if (magic) 518c2ecf20Sopenharmony_ci reboot->write(reboot, magic); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci return NOTIFY_DONE; 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci/** 578c2ecf20Sopenharmony_ci * reboot_mode_register - register a reboot mode driver 588c2ecf20Sopenharmony_ci * @reboot: reboot mode driver 598c2ecf20Sopenharmony_ci * 608c2ecf20Sopenharmony_ci * Returns: 0 on success or a negative error code on failure. 618c2ecf20Sopenharmony_ci */ 628c2ecf20Sopenharmony_ciint reboot_mode_register(struct reboot_mode_driver *reboot) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci struct mode_info *info; 658c2ecf20Sopenharmony_ci struct property *prop; 668c2ecf20Sopenharmony_ci struct device_node *np = reboot->dev->of_node; 678c2ecf20Sopenharmony_ci size_t len = strlen(PREFIX); 688c2ecf20Sopenharmony_ci int ret; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&reboot->head); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci for_each_property_of_node(np, prop) { 738c2ecf20Sopenharmony_ci if (strncmp(prop->name, PREFIX, len)) 748c2ecf20Sopenharmony_ci continue; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL); 778c2ecf20Sopenharmony_ci if (!info) { 788c2ecf20Sopenharmony_ci ret = -ENOMEM; 798c2ecf20Sopenharmony_ci goto error; 808c2ecf20Sopenharmony_ci } 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci if (of_property_read_u32(np, prop->name, &info->magic)) { 838c2ecf20Sopenharmony_ci dev_err(reboot->dev, "reboot mode %s without magic number\n", 848c2ecf20Sopenharmony_ci info->mode); 858c2ecf20Sopenharmony_ci devm_kfree(reboot->dev, info); 868c2ecf20Sopenharmony_ci continue; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci info->mode = kstrdup_const(prop->name + len, GFP_KERNEL); 908c2ecf20Sopenharmony_ci if (!info->mode) { 918c2ecf20Sopenharmony_ci ret = -ENOMEM; 928c2ecf20Sopenharmony_ci goto error; 938c2ecf20Sopenharmony_ci } else if (info->mode[0] == '\0') { 948c2ecf20Sopenharmony_ci kfree_const(info->mode); 958c2ecf20Sopenharmony_ci ret = -EINVAL; 968c2ecf20Sopenharmony_ci dev_err(reboot->dev, "invalid mode name(%s): too short!\n", 978c2ecf20Sopenharmony_ci prop->name); 988c2ecf20Sopenharmony_ci goto error; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci list_add_tail(&info->list, &reboot->head); 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci reboot->reboot_notifier.notifier_call = reboot_mode_notify; 1058c2ecf20Sopenharmony_ci register_reboot_notifier(&reboot->reboot_notifier); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci return 0; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cierror: 1108c2ecf20Sopenharmony_ci list_for_each_entry(info, &reboot->head, list) 1118c2ecf20Sopenharmony_ci kfree_const(info->mode); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci return ret; 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(reboot_mode_register); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci/** 1188c2ecf20Sopenharmony_ci * reboot_mode_unregister - unregister a reboot mode driver 1198c2ecf20Sopenharmony_ci * @reboot: reboot mode driver 1208c2ecf20Sopenharmony_ci */ 1218c2ecf20Sopenharmony_ciint reboot_mode_unregister(struct reboot_mode_driver *reboot) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct mode_info *info; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci unregister_reboot_notifier(&reboot->reboot_notifier); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci list_for_each_entry(info, &reboot->head, list) 1288c2ecf20Sopenharmony_ci kfree_const(info->mode); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci return 0; 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(reboot_mode_unregister); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic void devm_reboot_mode_release(struct device *dev, void *res) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci reboot_mode_unregister(*(struct reboot_mode_driver **)res); 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci/** 1408c2ecf20Sopenharmony_ci * devm_reboot_mode_register() - resource managed reboot_mode_register() 1418c2ecf20Sopenharmony_ci * @dev: device to associate this resource with 1428c2ecf20Sopenharmony_ci * @reboot: reboot mode driver 1438c2ecf20Sopenharmony_ci * 1448c2ecf20Sopenharmony_ci * Returns: 0 on success or a negative error code on failure. 1458c2ecf20Sopenharmony_ci */ 1468c2ecf20Sopenharmony_ciint devm_reboot_mode_register(struct device *dev, 1478c2ecf20Sopenharmony_ci struct reboot_mode_driver *reboot) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci struct reboot_mode_driver **dr; 1508c2ecf20Sopenharmony_ci int rc; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL); 1538c2ecf20Sopenharmony_ci if (!dr) 1548c2ecf20Sopenharmony_ci return -ENOMEM; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci rc = reboot_mode_register(reboot); 1578c2ecf20Sopenharmony_ci if (rc) { 1588c2ecf20Sopenharmony_ci devres_free(dr); 1598c2ecf20Sopenharmony_ci return rc; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci *dr = reboot; 1638c2ecf20Sopenharmony_ci devres_add(dev, dr); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci return 0; 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(devm_reboot_mode_register); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic int devm_reboot_mode_match(struct device *dev, void *res, void *data) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci struct reboot_mode_driver **p = res; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (WARN_ON(!p || !*p)) 1748c2ecf20Sopenharmony_ci return 0; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci return *p == data; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci/** 1808c2ecf20Sopenharmony_ci * devm_reboot_mode_unregister() - resource managed reboot_mode_unregister() 1818c2ecf20Sopenharmony_ci * @dev: device to associate this resource with 1828c2ecf20Sopenharmony_ci * @reboot: reboot mode driver 1838c2ecf20Sopenharmony_ci */ 1848c2ecf20Sopenharmony_civoid devm_reboot_mode_unregister(struct device *dev, 1858c2ecf20Sopenharmony_ci struct reboot_mode_driver *reboot) 1868c2ecf20Sopenharmony_ci{ 1878c2ecf20Sopenharmony_ci WARN_ON(devres_release(dev, 1888c2ecf20Sopenharmony_ci devm_reboot_mode_release, 1898c2ecf20Sopenharmony_ci devm_reboot_mode_match, reboot)); 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(devm_reboot_mode_unregister); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>"); 1948c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("System reboot mode core library"); 1958c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 196