1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (c) 2023 Neil Armstrong <neil.armstrong@linaro.org> |
4 | */ |
5 | #include <linux/kernel.h> |
6 | #include <linux/init.h> |
7 | #include <linux/of.h> |
8 | #include <linux/platform_device.h> |
9 | #include <linux/mfd/rk808.h> |
10 | #include <linux/regmap.h> |
11 | #include <linux/module.h> |
12 | #include <linux/reboot.h> |
13 | #include <linux/i2c.h> |
14 | |
15 | /* |
16 | * The Odroid Go Ultra has 2 PMICs: |
17 | * - RK818 (manages the battery and USB-C power supply) |
18 | * - RK817 |
19 | * Both PMICs feeds power to the S922X SoC, so they must be powered-off in sequence. |
20 | * Vendor does power-off the RK817 first, then the RK818 so here we follow this sequence. |
21 | */ |
22 | |
23 | struct odroid_go_ultra_poweroff_data { |
24 | struct device *dev; |
25 | struct device *rk817; |
26 | struct device *rk818; |
27 | }; |
28 | |
29 | static int odroid_go_ultra_poweroff_prepare(struct sys_off_data *data) |
30 | { |
31 | struct odroid_go_ultra_poweroff_data *poweroff_data = data->cb_data; |
32 | struct regmap *rk817, *rk818; |
33 | int ret; |
34 | |
35 | /* RK817 Regmap */ |
36 | rk817 = dev_get_regmap(dev: poweroff_data->rk817, NULL); |
37 | if (!rk817) { |
38 | dev_err(poweroff_data->dev, "failed to get rk817 regmap\n" ); |
39 | return notifier_from_errno(err: -EINVAL); |
40 | } |
41 | |
42 | /* RK818 Regmap */ |
43 | rk818 = dev_get_regmap(dev: poweroff_data->rk818, NULL); |
44 | if (!rk818) { |
45 | dev_err(poweroff_data->dev, "failed to get rk818 regmap\n" ); |
46 | return notifier_from_errno(err: -EINVAL); |
47 | } |
48 | |
49 | dev_info(poweroff_data->dev, "Setting PMICs for power off" ); |
50 | |
51 | /* RK817 */ |
52 | ret = regmap_update_bits(map: rk817, RK817_SYS_CFG(3), DEV_OFF, DEV_OFF); |
53 | if (ret) { |
54 | dev_err(poweroff_data->dev, "failed to poweroff rk817\n" ); |
55 | return notifier_from_errno(err: ret); |
56 | } |
57 | |
58 | /* RK818 */ |
59 | ret = regmap_update_bits(map: rk818, RK818_DEVCTRL_REG, DEV_OFF, DEV_OFF); |
60 | if (ret) { |
61 | dev_err(poweroff_data->dev, "failed to poweroff rk818\n" ); |
62 | return notifier_from_errno(err: ret); |
63 | } |
64 | |
65 | return NOTIFY_OK; |
66 | } |
67 | |
68 | static void odroid_go_ultra_poweroff_put_pmic_device(void *data) |
69 | { |
70 | struct device *dev = data; |
71 | |
72 | put_device(dev); |
73 | } |
74 | |
75 | static int odroid_go_ultra_poweroff_get_pmic_device(struct device *dev, const char *compatible, |
76 | struct device **pmic) |
77 | { |
78 | struct device_node *pmic_node; |
79 | struct i2c_client *pmic_client; |
80 | |
81 | pmic_node = of_find_compatible_node(NULL, NULL, compat: compatible); |
82 | if (!pmic_node) |
83 | return -ENODEV; |
84 | |
85 | pmic_client = of_find_i2c_device_by_node(node: pmic_node); |
86 | of_node_put(node: pmic_node); |
87 | if (!pmic_client) |
88 | return -EPROBE_DEFER; |
89 | |
90 | *pmic = &pmic_client->dev; |
91 | |
92 | return devm_add_action_or_reset(dev, odroid_go_ultra_poweroff_put_pmic_device, *pmic); |
93 | } |
94 | |
95 | static int odroid_go_ultra_poweroff_probe(struct platform_device *pdev) |
96 | { |
97 | struct odroid_go_ultra_poweroff_data *poweroff_data; |
98 | int ret; |
99 | |
100 | poweroff_data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*poweroff_data), GFP_KERNEL); |
101 | if (!poweroff_data) |
102 | return -ENOMEM; |
103 | |
104 | dev_set_drvdata(dev: &pdev->dev, data: poweroff_data); |
105 | |
106 | /* RK818 PMIC Device */ |
107 | ret = odroid_go_ultra_poweroff_get_pmic_device(dev: &pdev->dev, compatible: "rockchip,rk818" , |
108 | pmic: &poweroff_data->rk818); |
109 | if (ret) |
110 | return dev_err_probe(dev: &pdev->dev, err: ret, fmt: "failed to get rk818 mfd data\n" ); |
111 | |
112 | /* RK817 PMIC Device */ |
113 | ret = odroid_go_ultra_poweroff_get_pmic_device(dev: &pdev->dev, compatible: "rockchip,rk817" , |
114 | pmic: &poweroff_data->rk817); |
115 | if (ret) |
116 | return dev_err_probe(dev: &pdev->dev, err: ret, fmt: "failed to get rk817 mfd data\n" ); |
117 | |
118 | /* Register as SYS_OFF_MODE_POWER_OFF_PREPARE because regmap_update_bits may sleep */ |
119 | ret = devm_register_sys_off_handler(dev: &pdev->dev, |
120 | mode: SYS_OFF_MODE_POWER_OFF_PREPARE, |
121 | SYS_OFF_PRIO_DEFAULT, |
122 | callback: odroid_go_ultra_poweroff_prepare, |
123 | cb_data: poweroff_data); |
124 | if (ret) |
125 | return dev_err_probe(dev: &pdev->dev, err: ret, fmt: "failed to register sys-off handler\n" ); |
126 | |
127 | dev_info(&pdev->dev, "Registered Power-Off handler\n" ); |
128 | |
129 | return 0; |
130 | } |
131 | static struct platform_device *pdev; |
132 | |
133 | static struct platform_driver odroid_go_ultra_poweroff_driver = { |
134 | .driver = { |
135 | .name = "odroid-go-ultra-poweroff" , |
136 | }, |
137 | .probe = odroid_go_ultra_poweroff_probe, |
138 | }; |
139 | |
140 | static int __init odroid_go_ultra_poweroff_init(void) |
141 | { |
142 | int ret; |
143 | |
144 | /* Only create when running on the Odroid Go Ultra device */ |
145 | if (!of_device_is_compatible(device: of_root, "hardkernel,odroid-go-ultra" )) |
146 | return -ENODEV; |
147 | |
148 | ret = platform_driver_register(&odroid_go_ultra_poweroff_driver); |
149 | if (ret) |
150 | return ret; |
151 | |
152 | pdev = platform_device_register_resndata(NULL, name: "odroid-go-ultra-poweroff" , id: -1, |
153 | NULL, num: 0, NULL, size: 0); |
154 | |
155 | if (IS_ERR(ptr: pdev)) { |
156 | platform_driver_unregister(&odroid_go_ultra_poweroff_driver); |
157 | return PTR_ERR(ptr: pdev); |
158 | } |
159 | |
160 | return 0; |
161 | } |
162 | |
163 | static void __exit odroid_go_ultra_poweroff_exit(void) |
164 | { |
165 | /* Only delete when running on the Odroid Go Ultra device */ |
166 | if (!of_device_is_compatible(device: of_root, "hardkernel,odroid-go-ultra" )) |
167 | return; |
168 | |
169 | platform_device_unregister(pdev); |
170 | platform_driver_unregister(&odroid_go_ultra_poweroff_driver); |
171 | } |
172 | |
173 | module_init(odroid_go_ultra_poweroff_init); |
174 | module_exit(odroid_go_ultra_poweroff_exit); |
175 | |
176 | MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>" ); |
177 | MODULE_DESCRIPTION("Odroid Go Ultra poweroff driver" ); |
178 | MODULE_LICENSE("GPL" ); |
179 | |