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