summaryrefslogtreecommitdiff
path: root/drivers/power/reset/odroid-go-ultra-poweroff.c
blob: 9cac7aef77f0f0991e9d838b3cc4a081c02fc36c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2023 Neil Armstrong <neil.armstrong@linaro.org>
 */
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/mfd/rk808.h>
#include <linux/regmap.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/i2c.h>

/*
 * The Odroid Go Ultra has 2 PMICs:
 * - RK818 (manages the battery and USB-C power supply)
 * - RK817
 * Both PMICs feeds power to the S922X SoC, so they must be powered-off in sequence.
 * Vendor does power-off the RK817 first, then the RK818 so here we follow this sequence.
 */

struct odroid_go_ultra_poweroff_data {
	struct device *dev;
	struct device *rk817;
	struct device *rk818;
};

static int odroid_go_ultra_poweroff_prepare(struct sys_off_data *data)
{
	struct odroid_go_ultra_poweroff_data *poweroff_data = data->cb_data;
	struct regmap *rk817, *rk818;
	int ret;

	/* RK817 Regmap */
	rk817 = dev_get_regmap(poweroff_data->rk817, NULL);
	if (!rk817) {
		dev_err(poweroff_data->dev, "failed to get rk817 regmap\n");
		return notifier_from_errno(-EINVAL);
	}

	/* RK818 Regmap */
	rk818 = dev_get_regmap(poweroff_data->rk818, NULL);
	if (!rk818) {
		dev_err(poweroff_data->dev, "failed to get rk818 regmap\n");
		return notifier_from_errno(-EINVAL);
	}

	dev_info(poweroff_data->dev, "Setting PMICs for power off");

	/* RK817 */
	ret = regmap_update_bits(rk817, RK817_SYS_CFG(3), DEV_OFF, DEV_OFF);
	if (ret) {
		dev_err(poweroff_data->dev, "failed to poweroff rk817\n");
		return notifier_from_errno(ret);
	}

	/* RK818 */
	ret = regmap_update_bits(rk818, RK818_DEVCTRL_REG, DEV_OFF, DEV_OFF);
	if (ret) {
		dev_err(poweroff_data->dev, "failed to poweroff rk818\n");
		return notifier_from_errno(ret);
	}

	return NOTIFY_OK;
}

static void odroid_go_ultra_poweroff_put_pmic_device(void *data)
{
	struct device *dev = data;

	put_device(dev);
}

static int odroid_go_ultra_poweroff_get_pmic_device(struct device *dev, const char *compatible,
						    struct device **pmic)
{
	struct device_node *pmic_node;
	struct i2c_client *pmic_client;

	pmic_node = of_find_compatible_node(NULL, NULL, compatible);
	if (!pmic_node)
		return -ENODEV;

	pmic_client = of_find_i2c_device_by_node(pmic_node);
	of_node_put(pmic_node);
	if (!pmic_client)
		return -EPROBE_DEFER;

	*pmic = &pmic_client->dev;

	return devm_add_action_or_reset(dev, odroid_go_ultra_poweroff_put_pmic_device, *pmic);
}

static int odroid_go_ultra_poweroff_probe(struct platform_device *pdev)
{
	struct odroid_go_ultra_poweroff_data *poweroff_data;
	int ret;

	poweroff_data = devm_kzalloc(&pdev->dev, sizeof(*poweroff_data), GFP_KERNEL);
	if (!poweroff_data)
		return -ENOMEM;

	dev_set_drvdata(&pdev->dev, poweroff_data);

	/* RK818 PMIC Device */
	ret = odroid_go_ultra_poweroff_get_pmic_device(&pdev->dev, "rockchip,rk818",
						       &poweroff_data->rk818);
	if (ret)
		return dev_err_probe(&pdev->dev, ret, "failed to get rk818 mfd data\n");

	/* RK817 PMIC Device */
	ret = odroid_go_ultra_poweroff_get_pmic_device(&pdev->dev, "rockchip,rk817",
						       &poweroff_data->rk817);
	if (ret)
		return dev_err_probe(&pdev->dev, ret, "failed to get rk817 mfd data\n");

	/* Register as SYS_OFF_MODE_POWER_OFF_PREPARE because regmap_update_bits may sleep */
	ret = devm_register_sys_off_handler(&pdev->dev,
					    SYS_OFF_MODE_POWER_OFF_PREPARE,
					    SYS_OFF_PRIO_DEFAULT,
					    odroid_go_ultra_poweroff_prepare,
					    poweroff_data);
	if (ret)
		return dev_err_probe(&pdev->dev, ret, "failed to register sys-off handler\n");

	dev_info(&pdev->dev, "Registered Power-Off handler\n");

	return 0;
}
static struct platform_device *pdev;

static struct platform_driver odroid_go_ultra_poweroff_driver = {
	.driver = {
		.name	= "odroid-go-ultra-poweroff",
	},
	.probe = odroid_go_ultra_poweroff_probe,
};

static int __init odroid_go_ultra_poweroff_init(void)
{
	int ret;

	/* Only create when running on the Odroid Go Ultra device */
	if (!of_device_is_compatible(of_root, "hardkernel,odroid-go-ultra"))
		return -ENODEV;

	ret = platform_driver_register(&odroid_go_ultra_poweroff_driver);
	if (ret)
		return ret;

	pdev = platform_device_register_resndata(NULL, "odroid-go-ultra-poweroff", -1,
						 NULL, 0, NULL, 0);

	if (IS_ERR(pdev)) {
		platform_driver_unregister(&odroid_go_ultra_poweroff_driver);
		return PTR_ERR(pdev);
	}

	return 0;
}

static void __exit odroid_go_ultra_poweroff_exit(void)
{
	/* Only delete when running on the Odroid Go Ultra device */
	if (!of_device_is_compatible(of_root, "hardkernel,odroid-go-ultra"))
		return;

	platform_device_unregister(pdev);
	platform_driver_unregister(&odroid_go_ultra_poweroff_driver);
}

module_init(odroid_go_ultra_poweroff_init);
module_exit(odroid_go_ultra_poweroff_exit);

MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>");
MODULE_DESCRIPTION("Odroid Go Ultra poweroff driver");
MODULE_LICENSE("GPL");