xref: /linux/drivers/power/reset/qemu-virt-ctrl.c (revision 090748e62f57a80286b2fcc32fe2be069f891200)
1*ad9d2cd0SKuan-Wei Chiu // SPDX-License-Identifier: GPL-2.0
2*ad9d2cd0SKuan-Wei Chiu /*
3*ad9d2cd0SKuan-Wei Chiu  * QEMU Virt Machine System Controller Driver
4*ad9d2cd0SKuan-Wei Chiu  *
5*ad9d2cd0SKuan-Wei Chiu  * Copyright (C) 2026 Kuan-Wei Chiu <visitorckw@gmail.com>
6*ad9d2cd0SKuan-Wei Chiu  */
7*ad9d2cd0SKuan-Wei Chiu 
8*ad9d2cd0SKuan-Wei Chiu #include <linux/io.h>
9*ad9d2cd0SKuan-Wei Chiu #include <linux/module.h>
10*ad9d2cd0SKuan-Wei Chiu #include <linux/mod_devicetable.h>
11*ad9d2cd0SKuan-Wei Chiu #include <linux/platform_device.h>
12*ad9d2cd0SKuan-Wei Chiu #include <linux/reboot.h>
13*ad9d2cd0SKuan-Wei Chiu 
14*ad9d2cd0SKuan-Wei Chiu /* Registers */
15*ad9d2cd0SKuan-Wei Chiu #define VIRT_CTRL_REG_FEATURES	0x00
16*ad9d2cd0SKuan-Wei Chiu #define VIRT_CTRL_REG_CMD	0x04
17*ad9d2cd0SKuan-Wei Chiu 
18*ad9d2cd0SKuan-Wei Chiu /* Commands */
19*ad9d2cd0SKuan-Wei Chiu #define CMD_NOOP	0
20*ad9d2cd0SKuan-Wei Chiu #define CMD_RESET	1
21*ad9d2cd0SKuan-Wei Chiu #define CMD_HALT	2
22*ad9d2cd0SKuan-Wei Chiu #define CMD_PANIC	3
23*ad9d2cd0SKuan-Wei Chiu 
24*ad9d2cd0SKuan-Wei Chiu struct qemu_virt_ctrl {
25*ad9d2cd0SKuan-Wei Chiu 	void __iomem *base;
26*ad9d2cd0SKuan-Wei Chiu 	struct notifier_block reboot_nb;
27*ad9d2cd0SKuan-Wei Chiu };
28*ad9d2cd0SKuan-Wei Chiu 
29*ad9d2cd0SKuan-Wei Chiu static inline void virt_ctrl_write32(u32 val, void __iomem *addr)
30*ad9d2cd0SKuan-Wei Chiu {
31*ad9d2cd0SKuan-Wei Chiu 	if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
32*ad9d2cd0SKuan-Wei Chiu 		iowrite32be(val, addr);
33*ad9d2cd0SKuan-Wei Chiu 	else
34*ad9d2cd0SKuan-Wei Chiu 		iowrite32(val, addr);
35*ad9d2cd0SKuan-Wei Chiu }
36*ad9d2cd0SKuan-Wei Chiu 
37*ad9d2cd0SKuan-Wei Chiu static int qemu_virt_ctrl_power_off(struct sys_off_data *data)
38*ad9d2cd0SKuan-Wei Chiu {
39*ad9d2cd0SKuan-Wei Chiu 	struct qemu_virt_ctrl *ctrl = data->cb_data;
40*ad9d2cd0SKuan-Wei Chiu 
41*ad9d2cd0SKuan-Wei Chiu 	virt_ctrl_write32(CMD_HALT, ctrl->base + VIRT_CTRL_REG_CMD);
42*ad9d2cd0SKuan-Wei Chiu 
43*ad9d2cd0SKuan-Wei Chiu 	return NOTIFY_DONE;
44*ad9d2cd0SKuan-Wei Chiu }
45*ad9d2cd0SKuan-Wei Chiu 
46*ad9d2cd0SKuan-Wei Chiu static int qemu_virt_ctrl_restart(struct sys_off_data *data)
47*ad9d2cd0SKuan-Wei Chiu {
48*ad9d2cd0SKuan-Wei Chiu 	struct qemu_virt_ctrl *ctrl = data->cb_data;
49*ad9d2cd0SKuan-Wei Chiu 
50*ad9d2cd0SKuan-Wei Chiu 	virt_ctrl_write32(CMD_RESET, ctrl->base + VIRT_CTRL_REG_CMD);
51*ad9d2cd0SKuan-Wei Chiu 
52*ad9d2cd0SKuan-Wei Chiu 	return NOTIFY_DONE;
53*ad9d2cd0SKuan-Wei Chiu }
54*ad9d2cd0SKuan-Wei Chiu 
55*ad9d2cd0SKuan-Wei Chiu static int qemu_virt_ctrl_reboot_notify(struct notifier_block *nb,
56*ad9d2cd0SKuan-Wei Chiu 					unsigned long action, void *data)
57*ad9d2cd0SKuan-Wei Chiu {
58*ad9d2cd0SKuan-Wei Chiu 	struct qemu_virt_ctrl *ctrl = container_of(nb, struct qemu_virt_ctrl, reboot_nb);
59*ad9d2cd0SKuan-Wei Chiu 
60*ad9d2cd0SKuan-Wei Chiu 	if (action == SYS_HALT)
61*ad9d2cd0SKuan-Wei Chiu 		virt_ctrl_write32(CMD_HALT, ctrl->base + VIRT_CTRL_REG_CMD);
62*ad9d2cd0SKuan-Wei Chiu 
63*ad9d2cd0SKuan-Wei Chiu 	return NOTIFY_DONE;
64*ad9d2cd0SKuan-Wei Chiu }
65*ad9d2cd0SKuan-Wei Chiu 
66*ad9d2cd0SKuan-Wei Chiu static int qemu_virt_ctrl_probe(struct platform_device *pdev)
67*ad9d2cd0SKuan-Wei Chiu {
68*ad9d2cd0SKuan-Wei Chiu 	struct qemu_virt_ctrl *ctrl;
69*ad9d2cd0SKuan-Wei Chiu 	int ret;
70*ad9d2cd0SKuan-Wei Chiu 
71*ad9d2cd0SKuan-Wei Chiu 	ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL);
72*ad9d2cd0SKuan-Wei Chiu 	if (!ctrl)
73*ad9d2cd0SKuan-Wei Chiu 		return -ENOMEM;
74*ad9d2cd0SKuan-Wei Chiu 
75*ad9d2cd0SKuan-Wei Chiu 	ctrl->base = devm_platform_ioremap_resource(pdev, 0);
76*ad9d2cd0SKuan-Wei Chiu 	if (IS_ERR(ctrl->base))
77*ad9d2cd0SKuan-Wei Chiu 		return PTR_ERR(ctrl->base);
78*ad9d2cd0SKuan-Wei Chiu 
79*ad9d2cd0SKuan-Wei Chiu 	ret = devm_register_sys_off_handler(&pdev->dev,
80*ad9d2cd0SKuan-Wei Chiu 					    SYS_OFF_MODE_RESTART,
81*ad9d2cd0SKuan-Wei Chiu 					    SYS_OFF_PRIO_DEFAULT,
82*ad9d2cd0SKuan-Wei Chiu 					    qemu_virt_ctrl_restart,
83*ad9d2cd0SKuan-Wei Chiu 					    ctrl);
84*ad9d2cd0SKuan-Wei Chiu 	if (ret)
85*ad9d2cd0SKuan-Wei Chiu 		return dev_err_probe(&pdev->dev, ret,
86*ad9d2cd0SKuan-Wei Chiu 				     "cannot register restart handler\n");
87*ad9d2cd0SKuan-Wei Chiu 
88*ad9d2cd0SKuan-Wei Chiu 	ret = devm_register_sys_off_handler(&pdev->dev,
89*ad9d2cd0SKuan-Wei Chiu 					    SYS_OFF_MODE_POWER_OFF,
90*ad9d2cd0SKuan-Wei Chiu 					    SYS_OFF_PRIO_DEFAULT,
91*ad9d2cd0SKuan-Wei Chiu 					    qemu_virt_ctrl_power_off,
92*ad9d2cd0SKuan-Wei Chiu 					    ctrl);
93*ad9d2cd0SKuan-Wei Chiu 	if (ret)
94*ad9d2cd0SKuan-Wei Chiu 		return dev_err_probe(&pdev->dev, ret,
95*ad9d2cd0SKuan-Wei Chiu 				     "cannot register power-off handler\n");
96*ad9d2cd0SKuan-Wei Chiu 
97*ad9d2cd0SKuan-Wei Chiu 	ctrl->reboot_nb.notifier_call = qemu_virt_ctrl_reboot_notify;
98*ad9d2cd0SKuan-Wei Chiu 	ret = devm_register_reboot_notifier(&pdev->dev, &ctrl->reboot_nb);
99*ad9d2cd0SKuan-Wei Chiu 	if (ret)
100*ad9d2cd0SKuan-Wei Chiu 		return dev_err_probe(&pdev->dev, ret, "cannot register reboot notifier\n");
101*ad9d2cd0SKuan-Wei Chiu 
102*ad9d2cd0SKuan-Wei Chiu 	return 0;
103*ad9d2cd0SKuan-Wei Chiu }
104*ad9d2cd0SKuan-Wei Chiu 
105*ad9d2cd0SKuan-Wei Chiu static const struct platform_device_id qemu_virt_ctrl_id[] = {
106*ad9d2cd0SKuan-Wei Chiu 	{ "qemu-virt-ctrl", 0 },
107*ad9d2cd0SKuan-Wei Chiu 	{ }
108*ad9d2cd0SKuan-Wei Chiu };
109*ad9d2cd0SKuan-Wei Chiu MODULE_DEVICE_TABLE(platform, qemu_virt_ctrl_id);
110*ad9d2cd0SKuan-Wei Chiu 
111*ad9d2cd0SKuan-Wei Chiu static struct platform_driver qemu_virt_ctrl_driver = {
112*ad9d2cd0SKuan-Wei Chiu 	.probe = qemu_virt_ctrl_probe,
113*ad9d2cd0SKuan-Wei Chiu 	.driver = {
114*ad9d2cd0SKuan-Wei Chiu 		.name = "qemu-virt-ctrl",
115*ad9d2cd0SKuan-Wei Chiu 	},
116*ad9d2cd0SKuan-Wei Chiu 	.id_table = qemu_virt_ctrl_id,
117*ad9d2cd0SKuan-Wei Chiu };
118*ad9d2cd0SKuan-Wei Chiu module_platform_driver(qemu_virt_ctrl_driver);
119*ad9d2cd0SKuan-Wei Chiu 
120*ad9d2cd0SKuan-Wei Chiu MODULE_AUTHOR("Kuan-Wei Chiu <visitorckw@gmail.com>");
121*ad9d2cd0SKuan-Wei Chiu MODULE_DESCRIPTION("QEMU Virt Machine System Controller Driver");
122*ad9d2cd0SKuan-Wei Chiu MODULE_LICENSE("GPL");
123