Black God

How to get Scheduled Power On feature working in Debian GNU/Linux on QNAP TS-110 Server?

QNAP TS-110 SOHO Server does support Scheduled Power ON feature with default QNAP firmware. But the Debian ARM Linux for QNAP device does not support this feature.

While hacking further on kernel code and internet, I came to know that RTC Seiko S-35390A driver does not support wake-up alarm in default kernel. So I compiled the kernel with wake-up feature. This patch is available in QNAP open source repository itself. But many people have reported that still they don’t get it working. The tricky portion was different this driver does not register itself as capable of doing wake-up. Just added a piece of code to do that. It is done. Here is complete guide to get it done.

The problem

root@debian:~# dmesg | grep rtc
[ 23.595997] rtc-mv rtc-mv: internal RTC not ticking
[ 23.606916] rtc-s35390a 0-0030: rtc core: registered rtc-s35390a as rtc0
[ 23.807546] rtc-s35390a 0-0030: setting system clock to 2012-04-17 15:30:25 UTC (1334676625)

The above log shows that internal RTC of Marvell 88F6281 is not functioning. That is the reason rtc-mv (platform RTC driver) fails. Let’s leave it as it is, no problem. Any way we have another externl RTC of S35390a (a I2C RTC Driver), which can be used. As per above log it works and registered as rtc0.

root@debian:~# cat /proc/driver/rtc
rtc_time : 14:51:32
rtc_date : 2012-04-17
24hr : yes

There is no alarm related data in the above information. It means that wakeup alarm
is not working. Let’s analyze why it does not work.

root@debian:~# ls /sys/class/rtc/rtc0/wakealarm
ls: cannot access /sys/class/rtc/rtc0/wakealarm: No such file or directory

The above error message conveys that rtc0 does not support wakealarm. But QNAP TS-110 official Linux firmware supports Scheduled Power ON/OFF feature. So I guess this device should be capable.

How to fix this module for wake alarm functionality?

The rtc-s35390a driver is built into the the default Debian Linux 6.0 (squeeze) kernel 2.6.32 installed. So you need to compile the kernel to fix this issue. You need to apply the patch shown below and build the kernel. Here is a write-up on how to compile and flash a custom kernel for QNAP TS-110 Server.

Fix-1: There was no wakeup alarm functionality in rtc-s35390a driver. It is available from QNAP open source code

Fix-2: RTC S35390a is a I2C device. rtc0 should be set as capable of wakeup (device_set_wakeup_capable(&client->dev, 1);). it was missing in the debian default kernel. It is added to the source.

You can download the complete patch here

  rtc-s35390a.patch (5.7 KiB, 332 hits)

Patch file content:

--- /root/rtc-s35390a_org.c	2012-04-18 06:58:43.000000000 +0530
+++ drivers/rtc/rtc-s35390a.c	2012-04-20 00:16:56.000000000 +0530
@@ -15,10 +15,13 @@
 #include <linux/bitrev.h>
 #include <linux/bcd.h>
 #include <linux/slab.h>
+#include <linux/delay.h>
 
 #define S35390A_CMD_STATUS1	0
 #define S35390A_CMD_STATUS2	1
 #define S35390A_CMD_TIME1	2
+#define S35390A_CMD_INT1	4
+#define S35390A_CMD_INT2	5
 
 #define S35390A_BYTE_YEAR	0
 #define S35390A_BYTE_MONTH	1
@@ -158,8 +161,16 @@
 {
 	struct s35390a *s35390a = i2c_get_clientdata(client);
 	char buf[7];
+	char sts[1];
 	int i, err;
 
+	sts[0] = 0;
+        s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, sts, sizeof(sts));  // Disable INT2 Alarm interrupt
+        mdelay(10);
+        s35390a_get_reg(s35390a, S35390A_CMD_STATUS1, sts, sizeof(sts)); // clear interrupt
+        mdelay(10);
+	printk("Clear RTC Alarm interrupt.\n");
+
 	err = s35390a_get_reg(s35390a, S35390A_CMD_TIME1, buf, sizeof(buf));
 	if (err < 0)
 		return err;
@@ -194,9 +205,128 @@
 	return s35390a_set_datetime(to_i2c_client(dev), tm);
 }
 
+// Roylin add ----------------------------------------------------------------------
+static int alarm_sts = 0;
+static int s35390a_get_alarm_time(struct i2c_client *client, struct rtc_wkalrm *alrm)
+{
+        struct s35390a *s35390a = i2c_get_clientdata(client);
+        int  err;
+        char buf[3];
+        char sts[1];
+
+        sts[0] = 0;
+        s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, sts, sizeof(sts));  // Disable INT2 Alarm interrupt
+        mdelay(10);
+        s35390a_get_reg(s35390a, S35390A_CMD_STATUS1, sts, sizeof(sts)); // clear interrupt
+        mdelay(10);
+
+        sts[0] = 0x02;
+        err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, sts, sizeof(sts));  // Set Alarm time
+        if (err < 0)
+                return err;
+
+        mdelay(10);
+        err = s35390a_get_reg(s35390a, S35390A_CMD_INT2, buf, sizeof(buf));
+        if (err < 0)
+                return err;
+        mdelay(10);
+        alrm->time.tm_wday = bcd2bin(bitrev8(buf[0])&0x07);
+        alrm->time.tm_hour = bcd2bin(bitrev8(buf[1])&0x3F);
+        alrm->time.tm_min  = bcd2bin(bitrev8(buf[2])&0x7F);
+
+        if(alarm_sts == 0){ // if disable
+                sts[0] = 0;
+                s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, sts, sizeof(sts));  // Disable INT2 Alarm interrupt
+                mdelay(10);
+        }
+        printk("RTC Get alarm w:[%02d] [H:M][%02d:%02d]\n", alrm->time.tm_wday, alrm->time.tm_hour, alrm->time.tm_min);
+
+	return 0;
+}
+
+static int s35390a_set_alarm_time(struct i2c_client *client, struct rtc_wkalrm *alrm)
+{
+        struct s35390a *s35390a = i2c_get_clientdata(client);
+        char buf[3];
+        char sts[1];
+
+        sts[0] = 0;
+        s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, sts, sizeof(sts));  // Disable INT2 Alarm interrupt
+        mdelay(10);
+        s35390a_get_reg(s35390a, S35390A_CMD_STATUS1, sts, sizeof(sts));  // if interrupt flag is on, it will clear
+        mdelay(10);
+        sts[0] = 0x2;
+        s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, sts, sizeof(sts));  // Enable INT2 Alarm interrupt
+        mdelay(10);
+
+        buf[0] = bitrev8(bin2bcd(alrm->time.tm_wday)) | 0x1;
+        if(alrm->time.tm_hour >= 12)
+                buf[1] = bitrev8(bin2bcd(alrm->time.tm_hour)) | 0x3;
+        else
+                buf[1] = bitrev8(bin2bcd(alrm->time.tm_hour)) | 0x1;
+
+        buf[2] = bitrev8(bin2bcd(alrm->time.tm_min))  | 0x1;
+        s35390a_set_reg(s35390a, S35390A_CMD_INT2, buf, sizeof(buf));
+        mdelay(10);
+
+        alarm_sts = alrm->enabled;
+        if(alarm_sts == 0){ // if disable
+                sts[0] = 0;
+                s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, sts, sizeof(sts));  // Disable INT2 Alarm interrupt
+                mdelay(10);
+        }
+        printk("Alarm interrupt status : %x\n", alarm_sts);
+        printk("Set RTC Alarm => w:[%02X] h:m[%02X:%02X]\n", bin2bcd(alrm->time.tm_wday), bin2bcd(alrm->time.tm_hour), bin2bcd(alrm->time.tm_min));
+
+	return 0;
+}
+
+static int s35390a_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	return s35390a_set_alarm_time(to_i2c_client(dev), alrm);
+}
+
+static int s35390a_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	return s35390a_get_alarm_time(to_i2c_client(dev), alrm);
+}
+
+static int s35390a_rtc_ioctl(struct device *dev, unsigned int cmd,
+                            unsigned long arg)
+{
+//	struct rtc_time tm;
+        struct rtc_wkalrm alarm;
+        void __user *uarg = (void __user *) arg;
+
+        switch (cmd) {
+        case RTC_AIE_OFF:
+                break;
+        case RTC_AIE_ON:
+                break;
+        case RTC_UIE_OFF:
+                break;
+        case RTC_UIE_ON:
+                break;
+	case RTC_WKALM_SET:
+		if (copy_from_user(&alarm, uarg, sizeof(alarm)))
+                        return -EFAULT;
+                return s35390a_set_alarm_time(to_i2c_client(dev), &alarm);
+	case RTC_WKALM_RD:
+		printk("=====================================RTC_WKALM_RD=============================\n");
+		break;
+        default:
+                return -ENOIOCTLCMD;
+        }
+        return 0;
+}
+
+
 static const struct rtc_class_ops s35390a_rtc_ops = {
 	.read_time	= s35390a_rtc_read_time,
 	.set_time	= s35390a_rtc_set_time,
+	.read_alarm	= s35390a_rtc_read_alarm,
+	.set_alarm	= s35390a_rtc_set_alarm,
+	.ioctl		= s35390a_rtc_ioctl,
 };
 
 static struct i2c_driver s35390a_driver;
@@ -261,6 +391,8 @@
 	if (s35390a_get_datetime(client, &tm) < 0)
 		dev_warn(&client->dev, "clock needs to be set\n");
 
+        device_set_wakeup_capable(&client->dev, 1);
+
 	s35390a->rtc = rtc_device_register(s35390a_driver.driver.name,
 				&client->dev, &s35390a_rtc_ops, THIS_MODULE);
 

Once booted with new kernel you can confirm working of wakeup alarm with following commands.

root@debian:~# dmesg | grep -i rtc
[ 23.595940] rtc-mv rtc-mv: internal RTC not ticking
[ 23.626336] Clear RTC Alarm interrupt.
[ 23.630939] rtc-s35390a 0-0030: rtc core: registered rtc-s35390a as rtc0
[ 23.857173] Clear RTC Alarm interrupt.
[ 23.861555] rtc-s35390a 0-0030: setting system clock to 2012-04-19 19:42:55 UTC (1334864575)

root@debian:~# cat /proc/driver/rtc
rtc_time : 19:04:47
rtc_date : 2012-04-19
alrm_time : 18:10:00
alrm_date : 1900-01-**
alarm_IRQ : no
alrm_pending : no
24hr : yes

root@debian:~# ls -l /sys/class/rtc/rtc0/wakealarm
-rw-r–r– 1 root root 4096 Apr 20 00:35 /sys/class/rtc/rtc0/wakealarm

Now it is time to set wakeup alarm before shutting down the machine. Here is how it can be done.

echo 0 > /sys/class/rtc/rtc0/wakealarm
echo `date ‘+%s’ -d ‘+ 30 minutes’` > /sys/class/rtc/rtc0/wakealarm
cat /sys/class/rtc/rtc0/wakealarm

The above commands setup wake up 30 minutes from now. Shutdown the machine and you will see it auto power on after 30 minutes.

Enjoy the power of Linux!

Reference:
1. Good article on RTC Wakeup
2. Patch for wakealarm support
3. Kirkwood: Don’t initialize Marvell RTC for all boards
4. QNAP open source code

5 comments for “How to get Scheduled Power On feature working in Debian GNU/Linux on QNAP TS-110 Server?

  1. pierrepoulpe
    February 11, 2013 at 9:38 pm

    Excellent!
    It didn’t boot anymore the first time.
    I followed http://karuppuswamy.com/wordpress/2012/04/16/how-to-build-and-install-custom-linux-kernel-for-qnap-server-based-on-arm-kirkwood-platform/
    Not sure exactly why, but I remember having forgotten doing ;
    make modules_install
    before update-initramfs
    and then I deleted inird.img, run make modules_install, and continued from there.
    I restored original boot image.

    Second try I didn’t miss anything and it just worked fine… wonderful.

    Question, I have a second TS110 (backup mirroring), may I just copy vmlinuz and initrd.img, and flash-kernel, or do I need to compile again on the second one?

    • February 14, 2013 at 5:00 pm

      Question, I have a second TS110 (backup mirroring), may I just copy vmlinuz and initrd.img, and flash-kernel, or do I need to compile again on the second one?

      It is possible. Ensure that you # apt-get update && # apt-get upgrade both the devices before build and copying the new kernel.

  2. the dsc
    November 28, 2012 at 7:57 am

    I think you don’t need to echo date, you can just redirect date directly, ie: “date –date=”+5 minutes” +%s > /sys/class/rtc/rtc0/wakealarm”.

    Not that it’s tremendously important anyway.

  3. Jurriaan
    April 24, 2012 at 12:39 pm

    Excellent! Will you send this patch to the kernel maintainers?

    • Black God
      April 24, 2012 at 9:20 pm

      Yes, I have sent it to the maintainer, it is under review

Leave a Reply

Your email address will not be published. Required fields are marked *