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/regmap.h>
17
18 static struct regmap *map;
19 static u32 offset;
20 static u32 value;
21 static u32 mask;
22
syscon_poweroff(void)23 static void syscon_poweroff(void)
24 {
25 /* Issue the poweroff */
26 regmap_update_bits(map, offset, mask, value);
27
28 mdelay(1000);
29
30 pr_emerg("Unable to poweroff system\n");
31 }
32
syscon_poweroff_probe(struct platform_device * pdev)33 static int syscon_poweroff_probe(struct platform_device *pdev)
34 {
35 int mask_err, value_err;
36
37 map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
38 if (IS_ERR(map)) {
39 dev_err(&pdev->dev, "unable to get syscon");
40 return PTR_ERR(map);
41 }
42
43 if (of_property_read_u32(pdev->dev.of_node, "offset", &offset)) {
44 dev_err(&pdev->dev, "unable to read 'offset'");
45 return -EINVAL;
46 }
47
48 value_err = of_property_read_u32(pdev->dev.of_node, "value", &value);
49 mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask);
50 if (value_err && mask_err) {
51 dev_err(&pdev->dev, "unable to read 'value' and 'mask'");
52 return -EINVAL;
53 }
54
55 if (value_err) {
56 /* support old binding */
57 value = mask;
58 mask = 0xFFFFFFFF;
59 } else if (mask_err) {
60 /* support value without mask*/
61 mask = 0xFFFFFFFF;
62 }
63
64 if (pm_power_off) {
65 dev_err(&pdev->dev, "pm_power_off already claimed for %ps",
66 pm_power_off);
67 return -EBUSY;
68 }
69
70 pm_power_off = syscon_poweroff;
71
72 return 0;
73 }
74
syscon_poweroff_remove(struct platform_device * pdev)75 static int syscon_poweroff_remove(struct platform_device *pdev)
76 {
77 if (pm_power_off == syscon_poweroff)
78 pm_power_off = NULL;
79
80 return 0;
81 }
82
83 static const struct of_device_id syscon_poweroff_of_match[] = {
84 { .compatible = "syscon-poweroff" },
85 {}
86 };
87
88 static struct platform_driver syscon_poweroff_driver = {
89 .probe = syscon_poweroff_probe,
90 .remove = syscon_poweroff_remove,
91 .driver = {
92 .name = "syscon-poweroff",
93 .of_match_table = syscon_poweroff_of_match,
94 },
95 };
96
syscon_poweroff_register(void)97 static int __init syscon_poweroff_register(void)
98 {
99 return platform_driver_register(&syscon_poweroff_driver);
100 }
101 device_initcall(syscon_poweroff_register);
102