Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * QNAP Turbo NAS Board power off. Can also be used on Synology devices.
0004  *
0005  * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch>
0006  *
0007  * Based on the code from:
0008  *
0009  * Copyright (C) 2009  Martin Michlmayr <tbm@cyrius.com>
0010  * Copyright (C) 2008  Byron Bradley <byron.bbradley@gmail.com>
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     /* hijack UART1 and reset into sane state */
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     /* send the power-off command to PIC */
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     /* We need to know tclk in order to calculate the UART divisor */
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     /* Check that nothing else has already setup a handler */
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");