| /* | 
 |  * 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> | 
 | #include <linux/reboot-mode.h> | 
 |  | 
 | #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); | 
 |  | 
 | 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); | 
 |  | 
 | MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com"); | 
 | MODULE_DESCRIPTION("System reboot mode core library"); | 
 | MODULE_LICENSE("GPL v2"); |