aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Lunn <andrew.lunn@ruag.com>2012-12-28 07:25:09 -0500
committerAnton Vorontsov <anton@enomsg.org>2013-01-06 17:00:23 -0500
commite8fc721a9ab8dd6723063a92f5c5fdb5eaffbd6e (patch)
treeb00f842e79fdd1730e87395a7f1d1983d7cd0fd3
parent22f1229fe9a6790695f72b1cdba56fcaee867d75 (diff)
power/reset: Add a new driver to turn QNAP board power off
The QNAP NAS boxes have a microcontroller attached to the SoCs second serial port. By sending it a simple command, it will turn the power for the board off. This driver registers a function for pm_power_off to send such a command. Signed-off-by: Andrew Lunn <andrew@lunn.ch> Signed-off-by: Anton Vorontsov <anton@enomsg.org>
-rw-r--r--Documentation/devicetree/bindings/power_supply/qnap-poweroff.txt13
-rw-r--r--drivers/power/reset/Kconfig9
-rw-r--r--drivers/power/reset/Makefile1
-rw-r--r--drivers/power/reset/qnap-poweroff.c116
4 files changed, 139 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/power_supply/qnap-poweroff.txt b/Documentation/devicetree/bindings/power_supply/qnap-poweroff.txt
new file mode 100644
index 000000000000..9a599d27bd75
--- /dev/null
+++ b/Documentation/devicetree/bindings/power_supply/qnap-poweroff.txt
@@ -0,0 +1,13 @@
1* QNAP Power Off
2
3QNAP NAS devices have a microcontroller controlling the main power
4supply. This microcontroller is connected to UART1 of the Kirkwood and
5Orion5x SoCs. Sending the charactor 'A', at 19200 baud, tells the
6microcontroller to turn the power off. This driver adds a handler to
7pm_power_off which is called to turn the power off.
8
9Required Properties:
10- compatible: Should be "qnap,power-off"
11
12- reg: Address and length of the register set for UART1
13- clocks: tclk clock
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 6461b489fb09..6453aac701c0 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -13,3 +13,12 @@ config POWER_RESET_GPIO
13 This driver supports turning off your board via a GPIO line. 13 This driver supports turning off your board via a GPIO line.
14 If your board needs a GPIO high/low to power down, say Y and 14 If your board needs a GPIO high/low to power down, say Y and
15 create a binding in your devicetree. 15 create a binding in your devicetree.
16
17config POWER_RESET_QNAP
18 bool "QNAP power-off driver"
19 depends on OF_GPIO && POWER_RESET && PLAT_ORION
20 help
21 This driver supports turning off QNAP NAS devices by sending
22 commands to the microcontroller which controls the main power.
23
24 Say Y if you have a QNAP NAS.
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 751488a4a0c5..c4d3b2d11b9e 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -1 +1,2 @@
1obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o 1obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
2obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
diff --git a/drivers/power/reset/qnap-poweroff.c b/drivers/power/reset/qnap-poweroff.c
new file mode 100644
index 000000000000..cecb317cf2fe
--- /dev/null
+++ b/drivers/power/reset/qnap-poweroff.c
@@ -0,0 +1,116 @@
1/*
2 * QNAP Turbo NAS Board power off
3 *
4 * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch>
5 *
6 * Based on the code from:
7 *
8 * Copyright (C) 2009 Martin Michlmayr <tbm@cyrius.com>
9 * Copyright (C) 2008 Byron Bradley <byron.bbradley@gmail.com>
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version
14 * 2 of the License, or (at your option) any later version.
15 */
16
17#include <linux/kernel.h>
18#include <linux/module.h>
19#include <linux/platform_device.h>
20#include <linux/serial_reg.h>
21#include <linux/kallsyms.h>
22#include <linux/of.h>
23#include <linux/io.h>
24#include <linux/clk.h>
25
26#define UART1_REG(x) (base + ((UART_##x) << 2))
27
28static void __iomem *base;
29static unsigned long tclk;
30
31static void qnap_power_off(void)
32{
33 /* 19200 baud divisor */
34 const unsigned divisor = ((tclk + (8 * 19200)) / (16 * 19200));
35
36 pr_err("%s: triggering power-off...\n", __func__);
37
38 /* hijack UART1 and reset into sane state (19200,8n1) */
39 writel(0x83, UART1_REG(LCR));
40 writel(divisor & 0xff, UART1_REG(DLL));
41 writel((divisor >> 8) & 0xff, UART1_REG(DLM));
42 writel(0x03, UART1_REG(LCR));
43 writel(0x00, UART1_REG(IER));
44 writel(0x00, UART1_REG(FCR));
45 writel(0x00, UART1_REG(MCR));
46
47 /* send the power-off command 'A' to PIC */
48 writel('A', UART1_REG(TX));
49}
50
51static int qnap_power_off_probe(struct platform_device *pdev)
52{
53 struct resource *res;
54 struct clk *clk;
55 char symname[KSYM_NAME_LEN];
56
57 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
58 if (!res) {
59 dev_err(&pdev->dev, "Missing resource");
60 return -EINVAL;
61 }
62
63 base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
64 if (!base) {
65 dev_err(&pdev->dev, "Unable to map resource");
66 return -EINVAL;
67 }
68
69 /* We need to know tclk in order to calculate the UART divisor */
70 clk = devm_clk_get(&pdev->dev, NULL);
71 if (IS_ERR(clk)) {
72 dev_err(&pdev->dev, "Clk missing");
73 return PTR_ERR(clk);
74 }
75
76 tclk = clk_get_rate(clk);
77
78 /* Check that nothing else has already setup a handler */
79 if (pm_power_off) {
80 lookup_symbol_name((ulong)pm_power_off, symname);
81 dev_err(&pdev->dev,
82 "pm_power_off already claimed %p %s",
83 pm_power_off, symname);
84 return -EBUSY;
85 }
86 pm_power_off = qnap_power_off;
87
88 return 0;
89}
90
91static int qnap_power_off_remove(struct platform_device *pdev)
92{
93 pm_power_off = NULL;
94 return 0;
95}
96
97static const struct of_device_id qnap_power_off_of_match_table[] = {
98 { .compatible = "qnap,power-off", },
99 {}
100};
101MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table);
102
103static struct platform_driver qnap_power_off_driver = {
104 .probe = qnap_power_off_probe,
105 .remove = qnap_power_off_remove,
106 .driver = {
107 .owner = THIS_MODULE,
108 .name = "qnap_power_off",
109 .of_match_table = of_match_ptr(qnap_power_off_of_match_table),
110 },
111};
112module_platform_driver(qnap_power_off_driver);
113
114MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
115MODULE_DESCRIPTION("QNAP Power off driver");
116MODULE_LICENSE("GPLv2+");