18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * SPI slave handler controlling system state 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * This SPI slave handler allows remote control of system reboot, power off, 58c2ecf20Sopenharmony_ci * halt, and suspend. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Copyright (C) 2016-2017 Glider bvba 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 108c2ecf20Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 118c2ecf20Sopenharmony_ci * for more details. 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Usage (assuming /dev/spidev2.0 corresponds to the SPI master on the remote 148c2ecf20Sopenharmony_ci * system): 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * # reboot='\x7c\x50' 178c2ecf20Sopenharmony_ci * # poweroff='\x71\x3f' 188c2ecf20Sopenharmony_ci * # halt='\x38\x76' 198c2ecf20Sopenharmony_ci * # suspend='\x1b\x1b' 208c2ecf20Sopenharmony_ci * # spidev_test -D /dev/spidev2.0 -p $suspend # or $reboot, $poweroff, $halt 218c2ecf20Sopenharmony_ci */ 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include <linux/completion.h> 248c2ecf20Sopenharmony_ci#include <linux/module.h> 258c2ecf20Sopenharmony_ci#include <linux/reboot.h> 268c2ecf20Sopenharmony_ci#include <linux/suspend.h> 278c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* 308c2ecf20Sopenharmony_ci * The numbers are chosen to display something human-readable on two 7-segment 318c2ecf20Sopenharmony_ci * displays connected to two 74HC595 shift registers 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_ci#define CMD_REBOOT 0x7c50 /* rb */ 348c2ecf20Sopenharmony_ci#define CMD_POWEROFF 0x713f /* OF */ 358c2ecf20Sopenharmony_ci#define CMD_HALT 0x3876 /* HL */ 368c2ecf20Sopenharmony_ci#define CMD_SUSPEND 0x1b1b /* ZZ */ 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistruct spi_slave_system_control_priv { 398c2ecf20Sopenharmony_ci struct spi_device *spi; 408c2ecf20Sopenharmony_ci struct completion finished; 418c2ecf20Sopenharmony_ci struct spi_transfer xfer; 428c2ecf20Sopenharmony_ci struct spi_message msg; 438c2ecf20Sopenharmony_ci __be16 cmd; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic 478c2ecf20Sopenharmony_ciint spi_slave_system_control_submit(struct spi_slave_system_control_priv *priv); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic void spi_slave_system_control_complete(void *arg) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci struct spi_slave_system_control_priv *priv = arg; 528c2ecf20Sopenharmony_ci u16 cmd; 538c2ecf20Sopenharmony_ci int ret; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci if (priv->msg.status) 568c2ecf20Sopenharmony_ci goto terminate; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci cmd = be16_to_cpu(priv->cmd); 598c2ecf20Sopenharmony_ci switch (cmd) { 608c2ecf20Sopenharmony_ci case CMD_REBOOT: 618c2ecf20Sopenharmony_ci dev_info(&priv->spi->dev, "Rebooting system...\n"); 628c2ecf20Sopenharmony_ci kernel_restart(NULL); 638c2ecf20Sopenharmony_ci break; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci case CMD_POWEROFF: 668c2ecf20Sopenharmony_ci dev_info(&priv->spi->dev, "Powering off system...\n"); 678c2ecf20Sopenharmony_ci kernel_power_off(); 688c2ecf20Sopenharmony_ci break; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci case CMD_HALT: 718c2ecf20Sopenharmony_ci dev_info(&priv->spi->dev, "Halting system...\n"); 728c2ecf20Sopenharmony_ci kernel_halt(); 738c2ecf20Sopenharmony_ci break; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci case CMD_SUSPEND: 768c2ecf20Sopenharmony_ci dev_info(&priv->spi->dev, "Suspending system...\n"); 778c2ecf20Sopenharmony_ci pm_suspend(PM_SUSPEND_MEM); 788c2ecf20Sopenharmony_ci break; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci default: 818c2ecf20Sopenharmony_ci dev_warn(&priv->spi->dev, "Unknown command 0x%x\n", cmd); 828c2ecf20Sopenharmony_ci break; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci ret = spi_slave_system_control_submit(priv); 868c2ecf20Sopenharmony_ci if (ret) 878c2ecf20Sopenharmony_ci goto terminate; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_citerminate: 928c2ecf20Sopenharmony_ci dev_info(&priv->spi->dev, "Terminating\n"); 938c2ecf20Sopenharmony_ci complete(&priv->finished); 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic 978c2ecf20Sopenharmony_ciint spi_slave_system_control_submit(struct spi_slave_system_control_priv *priv) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci int ret; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci priv->msg.complete = spi_slave_system_control_complete; 1048c2ecf20Sopenharmony_ci priv->msg.context = priv; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci ret = spi_async(priv->spi, &priv->msg); 1078c2ecf20Sopenharmony_ci if (ret) 1088c2ecf20Sopenharmony_ci dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci return ret; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic int spi_slave_system_control_probe(struct spi_device *spi) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct spi_slave_system_control_priv *priv; 1168c2ecf20Sopenharmony_ci int ret; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); 1198c2ecf20Sopenharmony_ci if (!priv) 1208c2ecf20Sopenharmony_ci return -ENOMEM; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci priv->spi = spi; 1238c2ecf20Sopenharmony_ci init_completion(&priv->finished); 1248c2ecf20Sopenharmony_ci priv->xfer.rx_buf = &priv->cmd; 1258c2ecf20Sopenharmony_ci priv->xfer.len = sizeof(priv->cmd); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci ret = spi_slave_system_control_submit(priv); 1288c2ecf20Sopenharmony_ci if (ret) 1298c2ecf20Sopenharmony_ci return ret; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci spi_set_drvdata(spi, priv); 1328c2ecf20Sopenharmony_ci return 0; 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic int spi_slave_system_control_remove(struct spi_device *spi) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci struct spi_slave_system_control_priv *priv = spi_get_drvdata(spi); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci spi_slave_abort(spi); 1408c2ecf20Sopenharmony_ci wait_for_completion(&priv->finished); 1418c2ecf20Sopenharmony_ci return 0; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic struct spi_driver spi_slave_system_control_driver = { 1458c2ecf20Sopenharmony_ci .driver = { 1468c2ecf20Sopenharmony_ci .name = "spi-slave-system-control", 1478c2ecf20Sopenharmony_ci }, 1488c2ecf20Sopenharmony_ci .probe = spi_slave_system_control_probe, 1498c2ecf20Sopenharmony_ci .remove = spi_slave_system_control_remove, 1508c2ecf20Sopenharmony_ci}; 1518c2ecf20Sopenharmony_cimodule_spi_driver(spi_slave_system_control_driver); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ciMODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>"); 1548c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SPI slave handler controlling system state"); 1558c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 156