1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * QNAP Turbo NAS Board power off. Can also be used on Synology devices. |
4 | * |
5 | * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch> |
6 | * |
7 | * Based on the code from: |
8 | * |
9 | * Copyright (C) 2009 Martin Michlmayr <tbm@cyrius.com> |
10 | * Copyright (C) 2008 Byron Bradley <byron.bbradley@gmail.com> |
11 | */ |
12 | |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/serial_reg.h> |
17 | #include <linux/of.h> |
18 | #include <linux/io.h> |
19 | #include <linux/clk.h> |
20 | |
21 | #define UART1_REG(x) (base + ((UART_##x) << 2)) |
22 | |
23 | struct power_off_cfg { |
24 | u32 baud; |
25 | char cmd; |
26 | }; |
27 | |
28 | static const struct power_off_cfg qnap_power_off_cfg = { |
29 | .baud = 19200, |
30 | .cmd = 'A', |
31 | }; |
32 | |
33 | static const struct power_off_cfg synology_power_off_cfg = { |
34 | .baud = 9600, |
35 | .cmd = '1', |
36 | }; |
37 | |
38 | static const struct of_device_id qnap_power_off_of_match_table[] = { |
39 | { .compatible = "qnap,power-off" , |
40 | .data = &qnap_power_off_cfg, |
41 | }, |
42 | { .compatible = "synology,power-off" , |
43 | .data = &synology_power_off_cfg, |
44 | }, |
45 | {} |
46 | }; |
47 | MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table); |
48 | |
49 | static void __iomem *base; |
50 | static unsigned long tclk; |
51 | static const struct power_off_cfg *cfg; |
52 | |
53 | static void qnap_power_off(void) |
54 | { |
55 | const unsigned divisor = ((tclk + (8 * cfg->baud)) / (16 * cfg->baud)); |
56 | |
57 | pr_err("%s: triggering power-off...\n" , __func__); |
58 | |
59 | /* hijack UART1 and reset into sane state */ |
60 | writel(val: 0x83, UART1_REG(LCR)); |
61 | writel(val: divisor & 0xff, UART1_REG(DLL)); |
62 | writel(val: (divisor >> 8) & 0xff, UART1_REG(DLM)); |
63 | writel(val: 0x03, UART1_REG(LCR)); |
64 | writel(val: 0x00, UART1_REG(IER)); |
65 | writel(val: 0x00, UART1_REG(FCR)); |
66 | writel(val: 0x00, UART1_REG(MCR)); |
67 | |
68 | /* send the power-off command to PIC */ |
69 | writel(val: cfg->cmd, UART1_REG(TX)); |
70 | } |
71 | |
72 | static int qnap_power_off_probe(struct platform_device *pdev) |
73 | { |
74 | struct device_node *np = pdev->dev.of_node; |
75 | struct resource *res; |
76 | struct clk *clk; |
77 | |
78 | const struct of_device_id *match = |
79 | of_match_node(matches: qnap_power_off_of_match_table, node: np); |
80 | cfg = match->data; |
81 | |
82 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
83 | if (!res) { |
84 | dev_err(&pdev->dev, "Missing resource" ); |
85 | return -EINVAL; |
86 | } |
87 | |
88 | base = devm_ioremap(dev: &pdev->dev, offset: res->start, size: resource_size(res)); |
89 | if (!base) { |
90 | dev_err(&pdev->dev, "Unable to map resource" ); |
91 | return -EINVAL; |
92 | } |
93 | |
94 | /* We need to know tclk in order to calculate the UART divisor */ |
95 | clk = devm_clk_get(dev: &pdev->dev, NULL); |
96 | if (IS_ERR(ptr: clk)) { |
97 | dev_err(&pdev->dev, "Clk missing" ); |
98 | return PTR_ERR(ptr: clk); |
99 | } |
100 | |
101 | tclk = clk_get_rate(clk); |
102 | |
103 | /* Check that nothing else has already setup a handler */ |
104 | if (pm_power_off) { |
105 | dev_err(&pdev->dev, "pm_power_off already claimed for %ps" , |
106 | pm_power_off); |
107 | return -EBUSY; |
108 | } |
109 | pm_power_off = qnap_power_off; |
110 | |
111 | return 0; |
112 | } |
113 | |
114 | static int qnap_power_off_remove(struct platform_device *pdev) |
115 | { |
116 | pm_power_off = NULL; |
117 | return 0; |
118 | } |
119 | |
120 | static struct platform_driver qnap_power_off_driver = { |
121 | .probe = qnap_power_off_probe, |
122 | .remove = qnap_power_off_remove, |
123 | .driver = { |
124 | .name = "qnap_power_off" , |
125 | .of_match_table = of_match_ptr(qnap_power_off_of_match_table), |
126 | }, |
127 | }; |
128 | module_platform_driver(qnap_power_off_driver); |
129 | |
130 | MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>" ); |
131 | MODULE_DESCRIPTION("QNAP Power off driver" ); |
132 | MODULE_LICENSE("GPL v2" ); |
133 | |