2 * QNAP Turbo NAS Board power off
4 * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch>
6 * Based on the code from:
8 * Copyright (C) 2009 Martin Michlmayr <tbm@cyrius.com>
9 * Copyright (C) 2008 Byron Bradley <byron.bbradley@gmail.com>
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.
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>
24 #include <linux/clk.h>
26 #define UART1_REG(x) (base + ((UART_##x) << 2))
28 static void __iomem
*base
;
29 static unsigned long tclk
;
31 static void qnap_power_off(void)
33 /* 19200 baud divisor */
34 const unsigned divisor
= ((tclk
+ (8 * 19200)) / (16 * 19200));
36 pr_err("%s: triggering power-off...\n", __func__
);
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
));
47 /* send the power-off command 'A' to PIC */
48 writel('A', UART1_REG(TX
));
51 static int qnap_power_off_probe(struct platform_device
*pdev
)
55 char symname
[KSYM_NAME_LEN
];
57 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
59 dev_err(&pdev
->dev
, "Missing resource");
63 base
= devm_ioremap(&pdev
->dev
, res
->start
, resource_size(res
));
65 dev_err(&pdev
->dev
, "Unable to map resource");
69 /* We need to know tclk in order to calculate the UART divisor */
70 clk
= devm_clk_get(&pdev
->dev
, NULL
);
72 dev_err(&pdev
->dev
, "Clk missing");
76 tclk
= clk_get_rate(clk
);
78 /* Check that nothing else has already setup a handler */
80 lookup_symbol_name((ulong
)pm_power_off
, symname
);
82 "pm_power_off already claimed %p %s",
83 pm_power_off
, symname
);
86 pm_power_off
= qnap_power_off
;
91 static int qnap_power_off_remove(struct platform_device
*pdev
)
97 static const struct of_device_id qnap_power_off_of_match_table
[] = {
98 { .compatible
= "qnap,power-off", },
101 MODULE_DEVICE_TABLE(of
, qnap_power_off_of_match_table
);
103 static struct platform_driver qnap_power_off_driver
= {
104 .probe
= qnap_power_off_probe
,
105 .remove
= qnap_power_off_remove
,
107 .owner
= THIS_MODULE
,
108 .name
= "qnap_power_off",
109 .of_match_table
= of_match_ptr(qnap_power_off_of_match_table
),
112 module_platform_driver(qnap_power_off_driver
);
114 MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
115 MODULE_DESCRIPTION("QNAP Power off driver");
116 MODULE_LICENSE("GPL v2");