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