xref: /linux/drivers/power/reset/reboot-mode.c (revision 071bf69a0220253a44acb8b2a27f7a262b9a46bf)
1 /*
2  * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  */
9 
10 #include <linux/device.h>
11 #include <linux/init.h>
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/of.h>
15 #include <linux/reboot.h>
16 #include "reboot-mode.h"
17 
18 #define PREFIX "mode-"
19 
20 struct mode_info {
21 	const char *mode;
22 	u32 magic;
23 	struct list_head list;
24 };
25 
26 static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
27 					  const char *cmd)
28 {
29 	const char *normal = "normal";
30 	int magic = 0;
31 	struct mode_info *info;
32 
33 	if (!cmd)
34 		cmd = normal;
35 
36 	list_for_each_entry(info, &reboot->head, list) {
37 		if (!strcmp(info->mode, cmd)) {
38 			magic = info->magic;
39 			break;
40 		}
41 	}
42 
43 	return magic;
44 }
45 
46 static int reboot_mode_notify(struct notifier_block *this,
47 			      unsigned long mode, void *cmd)
48 {
49 	struct reboot_mode_driver *reboot;
50 	unsigned int magic;
51 
52 	reboot = container_of(this, struct reboot_mode_driver, reboot_notifier);
53 	magic = get_reboot_mode_magic(reboot, cmd);
54 	if (magic)
55 		reboot->write(reboot, magic);
56 
57 	return NOTIFY_DONE;
58 }
59 
60 /**
61  * reboot_mode_register - register a reboot mode driver
62  * @reboot: reboot mode driver
63  *
64  * Returns: 0 on success or a negative error code on failure.
65  */
66 int reboot_mode_register(struct reboot_mode_driver *reboot)
67 {
68 	struct mode_info *info;
69 	struct property *prop;
70 	struct device_node *np = reboot->dev->of_node;
71 	size_t len = strlen(PREFIX);
72 	int ret;
73 
74 	INIT_LIST_HEAD(&reboot->head);
75 
76 	for_each_property_of_node(np, prop) {
77 		if (strncmp(prop->name, PREFIX, len))
78 			continue;
79 
80 		info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL);
81 		if (!info) {
82 			ret = -ENOMEM;
83 			goto error;
84 		}
85 
86 		if (of_property_read_u32(np, prop->name, &info->magic)) {
87 			dev_err(reboot->dev, "reboot mode %s without magic number\n",
88 				info->mode);
89 			devm_kfree(reboot->dev, info);
90 			continue;
91 		}
92 
93 		info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
94 		if (!info->mode) {
95 			ret =  -ENOMEM;
96 			goto error;
97 		} else if (info->mode[0] == '\0') {
98 			kfree_const(info->mode);
99 			ret = -EINVAL;
100 			dev_err(reboot->dev, "invalid mode name(%s): too short!\n",
101 				prop->name);
102 			goto error;
103 		}
104 
105 		list_add_tail(&info->list, &reboot->head);
106 	}
107 
108 	reboot->reboot_notifier.notifier_call = reboot_mode_notify;
109 	register_reboot_notifier(&reboot->reboot_notifier);
110 
111 	return 0;
112 
113 error:
114 	list_for_each_entry(info, &reboot->head, list)
115 		kfree_const(info->mode);
116 
117 	return ret;
118 }
119 EXPORT_SYMBOL_GPL(reboot_mode_register);
120 
121 /**
122  * reboot_mode_unregister - unregister a reboot mode driver
123  * @reboot: reboot mode driver
124  */
125 int reboot_mode_unregister(struct reboot_mode_driver *reboot)
126 {
127 	struct mode_info *info;
128 
129 	unregister_reboot_notifier(&reboot->reboot_notifier);
130 
131 	list_for_each_entry(info, &reboot->head, list)
132 		kfree_const(info->mode);
133 
134 	return 0;
135 }
136 EXPORT_SYMBOL_GPL(reboot_mode_unregister);
137 
138 MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com");
139 MODULE_DESCRIPTION("System reboot mode core library");
140 MODULE_LICENSE("GPL v2");
141