xref: /openbmc/linux/drivers/power/reset/qnap-poweroff.c (revision cdd38c5f1ce4398ec58fec95904b75824daab7b5)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2e8fc721aSAndrew Lunn /*
3200c0a3eSAndrew Lunn  * QNAP Turbo NAS Board power off. Can also be used on Synology devices.
4e8fc721aSAndrew Lunn  *
5e8fc721aSAndrew Lunn  * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch>
6e8fc721aSAndrew Lunn  *
7e8fc721aSAndrew Lunn  * Based on the code from:
8e8fc721aSAndrew Lunn  *
9e8fc721aSAndrew Lunn  * Copyright (C) 2009  Martin Michlmayr <tbm@cyrius.com>
10e8fc721aSAndrew Lunn  * Copyright (C) 2008  Byron Bradley <byron.bbradley@gmail.com>
11e8fc721aSAndrew Lunn  */
12e8fc721aSAndrew Lunn 
13e8fc721aSAndrew Lunn #include <linux/kernel.h>
14e8fc721aSAndrew Lunn #include <linux/module.h>
15e8fc721aSAndrew Lunn #include <linux/platform_device.h>
16e8fc721aSAndrew Lunn #include <linux/serial_reg.h>
17e8fc721aSAndrew Lunn #include <linux/of.h>
18e8fc721aSAndrew Lunn #include <linux/io.h>
19e8fc721aSAndrew Lunn #include <linux/clk.h>
20e8fc721aSAndrew Lunn 
21e8fc721aSAndrew Lunn #define UART1_REG(x)	(base + ((UART_##x) << 2))
22e8fc721aSAndrew Lunn 
23200c0a3eSAndrew Lunn struct power_off_cfg {
24200c0a3eSAndrew Lunn 	u32 baud;
25200c0a3eSAndrew Lunn 	char cmd;
26200c0a3eSAndrew Lunn };
27200c0a3eSAndrew Lunn 
28200c0a3eSAndrew Lunn static const struct power_off_cfg qnap_power_off_cfg = {
29200c0a3eSAndrew Lunn 	.baud = 19200,
30200c0a3eSAndrew Lunn 	.cmd = 'A',
31200c0a3eSAndrew Lunn };
32200c0a3eSAndrew Lunn 
33200c0a3eSAndrew Lunn static const struct power_off_cfg synology_power_off_cfg = {
34200c0a3eSAndrew Lunn 	.baud = 9600,
35200c0a3eSAndrew Lunn 	.cmd = '1',
36200c0a3eSAndrew Lunn };
37200c0a3eSAndrew Lunn 
38200c0a3eSAndrew Lunn static const struct of_device_id qnap_power_off_of_match_table[] = {
39200c0a3eSAndrew Lunn 	{ .compatible = "qnap,power-off",
40200c0a3eSAndrew Lunn 	  .data = &qnap_power_off_cfg,
41200c0a3eSAndrew Lunn 	},
42200c0a3eSAndrew Lunn 	{ .compatible = "synology,power-off",
43200c0a3eSAndrew Lunn 	  .data = &synology_power_off_cfg,
44200c0a3eSAndrew Lunn 	},
45200c0a3eSAndrew Lunn 	{}
46200c0a3eSAndrew Lunn };
47200c0a3eSAndrew Lunn MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table);
48200c0a3eSAndrew Lunn 
49e8fc721aSAndrew Lunn static void __iomem *base;
50e8fc721aSAndrew Lunn static unsigned long tclk;
51200c0a3eSAndrew Lunn static const struct power_off_cfg *cfg;
52e8fc721aSAndrew Lunn 
qnap_power_off(void)53e8fc721aSAndrew Lunn static void qnap_power_off(void)
54e8fc721aSAndrew Lunn {
55200c0a3eSAndrew Lunn 	const unsigned divisor = ((tclk + (8 * cfg->baud)) / (16 * cfg->baud));
56e8fc721aSAndrew Lunn 
57e8fc721aSAndrew Lunn 	pr_err("%s: triggering power-off...\n", __func__);
58e8fc721aSAndrew Lunn 
59200c0a3eSAndrew Lunn 	/* hijack UART1 and reset into sane state */
60e8fc721aSAndrew Lunn 	writel(0x83, UART1_REG(LCR));
61e8fc721aSAndrew Lunn 	writel(divisor & 0xff, UART1_REG(DLL));
62e8fc721aSAndrew Lunn 	writel((divisor >> 8) & 0xff, UART1_REG(DLM));
63e8fc721aSAndrew Lunn 	writel(0x03, UART1_REG(LCR));
64e8fc721aSAndrew Lunn 	writel(0x00, UART1_REG(IER));
65e8fc721aSAndrew Lunn 	writel(0x00, UART1_REG(FCR));
66e8fc721aSAndrew Lunn 	writel(0x00, UART1_REG(MCR));
67e8fc721aSAndrew Lunn 
68200c0a3eSAndrew Lunn 	/* send the power-off command to PIC */
69200c0a3eSAndrew Lunn 	writel(cfg->cmd, UART1_REG(TX));
70e8fc721aSAndrew Lunn }
71e8fc721aSAndrew Lunn 
qnap_power_off_probe(struct platform_device * pdev)72e8fc721aSAndrew Lunn static int qnap_power_off_probe(struct platform_device *pdev)
73e8fc721aSAndrew Lunn {
74200c0a3eSAndrew Lunn 	struct device_node *np = pdev->dev.of_node;
75e8fc721aSAndrew Lunn 	struct resource *res;
76e8fc721aSAndrew Lunn 	struct clk *clk;
77e8fc721aSAndrew Lunn 
78200c0a3eSAndrew Lunn 	const struct of_device_id *match =
79200c0a3eSAndrew Lunn 		of_match_node(qnap_power_off_of_match_table, np);
80200c0a3eSAndrew Lunn 	cfg = match->data;
81200c0a3eSAndrew Lunn 
82e8fc721aSAndrew Lunn 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
83e8fc721aSAndrew Lunn 	if (!res) {
84e8fc721aSAndrew Lunn 		dev_err(&pdev->dev, "Missing resource");
85e8fc721aSAndrew Lunn 		return -EINVAL;
86e8fc721aSAndrew Lunn 	}
87e8fc721aSAndrew Lunn 
88e8fc721aSAndrew Lunn 	base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
89e8fc721aSAndrew Lunn 	if (!base) {
90e8fc721aSAndrew Lunn 		dev_err(&pdev->dev, "Unable to map resource");
91e8fc721aSAndrew Lunn 		return -EINVAL;
92e8fc721aSAndrew Lunn 	}
93e8fc721aSAndrew Lunn 
94e8fc721aSAndrew Lunn 	/* We need to know tclk in order to calculate the UART divisor */
95e8fc721aSAndrew Lunn 	clk = devm_clk_get(&pdev->dev, NULL);
96e8fc721aSAndrew Lunn 	if (IS_ERR(clk)) {
97e8fc721aSAndrew Lunn 		dev_err(&pdev->dev, "Clk missing");
98e8fc721aSAndrew Lunn 		return PTR_ERR(clk);
99e8fc721aSAndrew Lunn 	}
100e8fc721aSAndrew Lunn 
101e8fc721aSAndrew Lunn 	tclk = clk_get_rate(clk);
102e8fc721aSAndrew Lunn 
103e8fc721aSAndrew Lunn 	/* Check that nothing else has already setup a handler */
104e8fc721aSAndrew Lunn 	if (pm_power_off) {
105*36dbca14SHelge Deller 		dev_err(&pdev->dev, "pm_power_off already claimed for %ps",
106*36dbca14SHelge Deller 			pm_power_off);
107e8fc721aSAndrew Lunn 		return -EBUSY;
108e8fc721aSAndrew Lunn 	}
109e8fc721aSAndrew Lunn 	pm_power_off = qnap_power_off;
110e8fc721aSAndrew Lunn 
111e8fc721aSAndrew Lunn 	return 0;
112e8fc721aSAndrew Lunn }
113e8fc721aSAndrew Lunn 
qnap_power_off_remove(struct platform_device * pdev)114e8fc721aSAndrew Lunn static int qnap_power_off_remove(struct platform_device *pdev)
115e8fc721aSAndrew Lunn {
116e8fc721aSAndrew Lunn 	pm_power_off = NULL;
117e8fc721aSAndrew Lunn 	return 0;
118e8fc721aSAndrew Lunn }
119e8fc721aSAndrew Lunn 
120e8fc721aSAndrew Lunn static struct platform_driver qnap_power_off_driver = {
121e8fc721aSAndrew Lunn 	.probe	= qnap_power_off_probe,
122e8fc721aSAndrew Lunn 	.remove	= qnap_power_off_remove,
123e8fc721aSAndrew Lunn 	.driver	= {
124e8fc721aSAndrew Lunn 		.name	= "qnap_power_off",
125e8fc721aSAndrew Lunn 		.of_match_table = of_match_ptr(qnap_power_off_of_match_table),
126e8fc721aSAndrew Lunn 	},
127e8fc721aSAndrew Lunn };
128e8fc721aSAndrew Lunn module_platform_driver(qnap_power_off_driver);
129e8fc721aSAndrew Lunn 
130e8fc721aSAndrew Lunn MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
131e8fc721aSAndrew Lunn MODULE_DESCRIPTION("QNAP Power off driver");
1328fd526fdSAndrew Lunn MODULE_LICENSE("GPL v2");
133