1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Generic Syscon Poweroff Driver |
4 | * |
5 | * Copyright (c) 2015, National Instruments Corp. |
6 | * Author: Moritz Fischer <moritz.fischer@ettus.com> |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/io.h> |
11 | #include <linux/notifier.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/of.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/pm.h> |
16 | #include <linux/reboot.h> |
17 | #include <linux/regmap.h> |
18 | |
19 | struct syscon_poweroff_data { |
20 | struct regmap *map; |
21 | u32 offset; |
22 | u32 value; |
23 | u32 mask; |
24 | }; |
25 | |
26 | static int syscon_poweroff(struct sys_off_data *off_data) |
27 | { |
28 | struct syscon_poweroff_data *data = off_data->cb_data; |
29 | |
30 | /* Issue the poweroff */ |
31 | regmap_update_bits(map: data->map, reg: data->offset, mask: data->mask, val: data->value); |
32 | |
33 | mdelay(1000); |
34 | |
35 | pr_emerg("Unable to poweroff system\n" ); |
36 | |
37 | return NOTIFY_DONE; |
38 | } |
39 | |
40 | static int syscon_poweroff_probe(struct platform_device *pdev) |
41 | { |
42 | struct device *dev = &pdev->dev; |
43 | struct syscon_poweroff_data *data; |
44 | int mask_err, value_err; |
45 | |
46 | data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL); |
47 | if (!data) |
48 | return -ENOMEM; |
49 | |
50 | data->map = syscon_regmap_lookup_by_phandle(np: dev->of_node, property: "regmap" ); |
51 | if (IS_ERR(ptr: data->map)) { |
52 | data->map = syscon_node_to_regmap(np: dev->parent->of_node); |
53 | if (IS_ERR(ptr: data->map)) { |
54 | dev_err(dev, "unable to get syscon" ); |
55 | return PTR_ERR(ptr: data->map); |
56 | } |
57 | } |
58 | |
59 | if (of_property_read_u32(np: dev->of_node, propname: "offset" , out_value: &data->offset)) { |
60 | dev_err(dev, "unable to read 'offset'" ); |
61 | return -EINVAL; |
62 | } |
63 | |
64 | value_err = of_property_read_u32(np: dev->of_node, propname: "value" , out_value: &data->value); |
65 | mask_err = of_property_read_u32(np: dev->of_node, propname: "mask" , out_value: &data->mask); |
66 | if (value_err && mask_err) { |
67 | dev_err(dev, "unable to read 'value' and 'mask'" ); |
68 | return -EINVAL; |
69 | } |
70 | |
71 | if (value_err) { |
72 | /* support old binding */ |
73 | data->value = data->mask; |
74 | data->mask = 0xFFFFFFFF; |
75 | } else if (mask_err) { |
76 | /* support value without mask*/ |
77 | data->mask = 0xFFFFFFFF; |
78 | } |
79 | |
80 | return devm_register_sys_off_handler(dev: &pdev->dev, |
81 | mode: SYS_OFF_MODE_POWER_OFF, |
82 | SYS_OFF_PRIO_DEFAULT, |
83 | callback: syscon_poweroff, cb_data: data); |
84 | } |
85 | |
86 | static const struct of_device_id syscon_poweroff_of_match[] = { |
87 | { .compatible = "syscon-poweroff" }, |
88 | {} |
89 | }; |
90 | |
91 | static struct platform_driver syscon_poweroff_driver = { |
92 | .probe = syscon_poweroff_probe, |
93 | .driver = { |
94 | .name = "syscon-poweroff" , |
95 | .of_match_table = syscon_poweroff_of_match, |
96 | }, |
97 | }; |
98 | builtin_platform_driver(syscon_poweroff_driver); |
99 | |