Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 350604
b: refs/heads/master
c: e8fc721
h: refs/heads/master
v: v3
  • Loading branch information
Andrew Lunn authored and Anton Vorontsov committed Jan 6, 2013
1 parent 86b9d9c commit c7618be
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 1 deletion.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 22f1229fe9a6790695f72b1cdba56fcaee867d75
refs/heads/master: e8fc721a9ab8dd6723063a92f5c5fdb5eaffbd6e
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
* QNAP Power Off

QNAP NAS devices have a microcontroller controlling the main power
supply. This microcontroller is connected to UART1 of the Kirkwood and
Orion5x SoCs. Sending the charactor 'A', at 19200 baud, tells the
microcontroller to turn the power off. This driver adds a handler to
pm_power_off which is called to turn the power off.

Required Properties:
- compatible: Should be "qnap,power-off"

- reg: Address and length of the register set for UART1
- clocks: tclk clock
9 changes: 9 additions & 0 deletions trunk/drivers/power/reset/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@ config POWER_RESET_GPIO
This driver supports turning off your board via a GPIO line.
If your board needs a GPIO high/low to power down, say Y and
create a binding in your devicetree.

config POWER_RESET_QNAP
bool "QNAP power-off driver"
depends on OF_GPIO && POWER_RESET && PLAT_ORION
help
This driver supports turning off QNAP NAS devices by sending
commands to the microcontroller which controls the main power.

Say Y if you have a QNAP NAS.
1 change: 1 addition & 0 deletions trunk/drivers/power/reset/Makefile
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
116 changes: 116 additions & 0 deletions trunk/drivers/power/reset/qnap-poweroff.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* QNAP Turbo NAS Board power off
*
* Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch>
*
* Based on the code from:
*
* Copyright (C) 2009 Martin Michlmayr <tbm@cyrius.com>
* Copyright (C) 2008 Byron Bradley <byron.bbradley@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/serial_reg.h>
#include <linux/kallsyms.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/clk.h>

#define UART1_REG(x) (base + ((UART_##x) << 2))

static void __iomem *base;
static unsigned long tclk;

static void qnap_power_off(void)
{
/* 19200 baud divisor */
const unsigned divisor = ((tclk + (8 * 19200)) / (16 * 19200));

pr_err("%s: triggering power-off...\n", __func__);

/* hijack UART1 and reset into sane state (19200,8n1) */
writel(0x83, UART1_REG(LCR));
writel(divisor & 0xff, UART1_REG(DLL));
writel((divisor >> 8) & 0xff, UART1_REG(DLM));
writel(0x03, UART1_REG(LCR));
writel(0x00, UART1_REG(IER));
writel(0x00, UART1_REG(FCR));
writel(0x00, UART1_REG(MCR));

/* send the power-off command 'A' to PIC */
writel('A', UART1_REG(TX));
}

static int qnap_power_off_probe(struct platform_device *pdev)
{
struct resource *res;
struct clk *clk;
char symname[KSYM_NAME_LEN];

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Missing resource");
return -EINVAL;
}

base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!base) {
dev_err(&pdev->dev, "Unable to map resource");
return -EINVAL;
}

/* We need to know tclk in order to calculate the UART divisor */
clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "Clk missing");
return PTR_ERR(clk);
}

tclk = clk_get_rate(clk);

/* Check that nothing else has already setup a handler */
if (pm_power_off) {
lookup_symbol_name((ulong)pm_power_off, symname);
dev_err(&pdev->dev,
"pm_power_off already claimed %p %s",
pm_power_off, symname);
return -EBUSY;
}
pm_power_off = qnap_power_off;

return 0;
}

static int qnap_power_off_remove(struct platform_device *pdev)
{
pm_power_off = NULL;
return 0;
}

static const struct of_device_id qnap_power_off_of_match_table[] = {
{ .compatible = "qnap,power-off", },
{}
};
MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table);

static struct platform_driver qnap_power_off_driver = {
.probe = qnap_power_off_probe,
.remove = qnap_power_off_remove,
.driver = {
.owner = THIS_MODULE,
.name = "qnap_power_off",
.of_match_table = of_match_ptr(qnap_power_off_of_match_table),
},
};
module_platform_driver(qnap_power_off_driver);

MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
MODULE_DESCRIPTION("QNAP Power off driver");
MODULE_LICENSE("GPLv2+");

0 comments on commit c7618be

Please sign in to comment.