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