0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013 #include <linux/kernel.h>
0014 #include <linux/module.h>
0015 #include <linux/platform_device.h>
0016 #include <linux/serial_reg.h>
0017 #include <linux/of.h>
0018 #include <linux/io.h>
0019 #include <linux/clk.h>
0020
0021 #define UART1_REG(x) (base + ((UART_##x) << 2))
0022
0023 struct power_off_cfg {
0024 u32 baud;
0025 char cmd;
0026 };
0027
0028 static const struct power_off_cfg qnap_power_off_cfg = {
0029 .baud = 19200,
0030 .cmd = 'A',
0031 };
0032
0033 static const struct power_off_cfg synology_power_off_cfg = {
0034 .baud = 9600,
0035 .cmd = '1',
0036 };
0037
0038 static const struct of_device_id qnap_power_off_of_match_table[] = {
0039 { .compatible = "qnap,power-off",
0040 .data = &qnap_power_off_cfg,
0041 },
0042 { .compatible = "synology,power-off",
0043 .data = &synology_power_off_cfg,
0044 },
0045 {}
0046 };
0047 MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table);
0048
0049 static void __iomem *base;
0050 static unsigned long tclk;
0051 static const struct power_off_cfg *cfg;
0052
0053 static void qnap_power_off(void)
0054 {
0055 const unsigned divisor = ((tclk + (8 * cfg->baud)) / (16 * cfg->baud));
0056
0057 pr_err("%s: triggering power-off...\n", __func__);
0058
0059
0060 writel(0x83, UART1_REG(LCR));
0061 writel(divisor & 0xff, UART1_REG(DLL));
0062 writel((divisor >> 8) & 0xff, UART1_REG(DLM));
0063 writel(0x03, UART1_REG(LCR));
0064 writel(0x00, UART1_REG(IER));
0065 writel(0x00, UART1_REG(FCR));
0066 writel(0x00, UART1_REG(MCR));
0067
0068
0069 writel(cfg->cmd, UART1_REG(TX));
0070 }
0071
0072 static int qnap_power_off_probe(struct platform_device *pdev)
0073 {
0074 struct device_node *np = pdev->dev.of_node;
0075 struct resource *res;
0076 struct clk *clk;
0077
0078 const struct of_device_id *match =
0079 of_match_node(qnap_power_off_of_match_table, np);
0080 cfg = match->data;
0081
0082 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
0083 if (!res) {
0084 dev_err(&pdev->dev, "Missing resource");
0085 return -EINVAL;
0086 }
0087
0088 base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
0089 if (!base) {
0090 dev_err(&pdev->dev, "Unable to map resource");
0091 return -EINVAL;
0092 }
0093
0094
0095 clk = devm_clk_get(&pdev->dev, NULL);
0096 if (IS_ERR(clk)) {
0097 dev_err(&pdev->dev, "Clk missing");
0098 return PTR_ERR(clk);
0099 }
0100
0101 tclk = clk_get_rate(clk);
0102
0103
0104 if (pm_power_off) {
0105 dev_err(&pdev->dev, "pm_power_off already claimed for %ps",
0106 pm_power_off);
0107 return -EBUSY;
0108 }
0109 pm_power_off = qnap_power_off;
0110
0111 return 0;
0112 }
0113
0114 static int qnap_power_off_remove(struct platform_device *pdev)
0115 {
0116 pm_power_off = NULL;
0117 return 0;
0118 }
0119
0120 static struct platform_driver qnap_power_off_driver = {
0121 .probe = qnap_power_off_probe,
0122 .remove = qnap_power_off_remove,
0123 .driver = {
0124 .name = "qnap_power_off",
0125 .of_match_table = of_match_ptr(qnap_power_off_of_match_table),
0126 },
0127 };
0128 module_platform_driver(qnap_power_off_driver);
0129
0130 MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
0131 MODULE_DESCRIPTION("QNAP Power off driver");
0132 MODULE_LICENSE("GPL v2");