/*
  wakeup_clock, a tool to set the automatic wakeup time on ATX mainboards
  Copyright (C) 2003,2004,2005  Wolfram Gloger

  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.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/* $Id: wakeup_clock.c,v 1.14 2006/12/31 23:42:07 wg Exp $ */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>		/* for geteuid() */
#include <fcntl.h>
#include <time.h>

#include <sys/stat.h>
#include <asm/io.h>		/* for inb, outb */

/************************************************************************/

/* Register numbers.  */
#define RTC_SECONDS		0
#define RTC_SECONDS_ALARM	1
#define RTC_MINUTES		2
#define RTC_MINUTES_ALARM	3
#define RTC_HOURS		4
#define RTC_HOURS_ALARM		5
#define RTC_CONTROL		11
#define RTC_INTR_FLAGS		12

#define ASUS_WAKEUP_DAY		13

#define CMOS_CHECKSUM_REG_MSB   62 /* these two seem to be the most */
#define CMOS_CHECKSUM_REG_LSB   63 /* common checksum registers */
   
#define ASUS_AUTO_POWERUP	78 /* Bit 1: enable ? */
#define ASUS_AUTO_POWERUP2	123 /* Bit 1: enable ? */

#define ECS_K7VZA_WAKEUP_SEC    77
#define ECS_K7VZA_WAKEUP_MIN    78
#define ECS_K7VZA_WAKEUP_HOUR   79
#define ECS_K7VZA_AUTO_POWERUP  85 /* Bit 5? */
#define ECS_K7VZA_WAKEUP_DAY    95
#define ECS_K7VZA_CHECKSUM_REG  124

/* MSI 6619 */
/* Thanks Magnus Sandin for info on ABIT IS7-E2.  */
#define MSI_WAKEUP_DAY          85
#define MSI_WAKEUP_HOUR         86
#define MSI_WAKEUP_MIN		87
#define MSI_WAKEUP_SEC		88
#define MSI_CHECKSUM_REG	126

#define ECS_K7S5A_WAKEUP_DAY	126

#define MSI_K7TTURBO2_WAKEUP_SEC     77
#define MSI_K7TTURBO2_WAKEUP_MIN     78
#define MSI_K7TTURBO2_WAKEUP_HOUR    79
#define MSI_K7TTURBO2_WAKEUP_DAY     95
#define MSI_K7TTURBO2_CHECKSUM_REG   124

/* ASRock K7VT2 */
#define ASROCK_K7VT2_WAKEUP_SEC      106 /* binary, bit 7 set? */
#define ASROCK_K7VT2_WAKEUP_MIN      105
#define ASROCK_K7VT2_WAKEUP_HOUR     104
#define ASROCK_K7VT2_WAKEUP_DAY1     103 /* binary, day*8+6  */
#define ASROCK_K7VT2_WAKEUP_DAY2     125 /* BCD */

/* ASRock K7VT6 */
#define ASROCK_K7VT6_WAKEUP_SEC      104 /* binary */
#define ASROCK_K7VT6_WAKEUP_MIN      103
#define ASROCK_K7VT6_WAKEUP_HOUR     102 /* binary, 128|hour */
#define ASROCK_K7VT6_WAKEUP_DAY1     101 /* binary  */
#define ASROCK_K7VT6_WAKEUP_DAY2     125 /* BCD */
/* checksum is same as K7VT2 */

/* Thanks Calvin Harrigan.  */
#define ASUS_A7V8X_X_WAKEUP_DAY1      91
#define ASUS_A7V8X_X_WAKEUP_DAY2      92
#define ASUS_A7V8X_X_CHECKSUM_REG1   121
#define ASUS_A7V8X_X_CHECKSUM_REG2   122

/* VIA EPIA M M10000, thanks Ken Staton.  */
#define VIA_EPIA_M_WAKEUP_SEC     93
#define VIA_EPIA_M_WAKEUP_MIN     92
#define VIA_EPIA_M_WAKEUP_HOUR    91
#define VIA_EPIA_M_WAKEUP_DAY     90
#define VIA_EPIA_M_CHECKSUM_REG   124

/* ASUS P4SGX, thanks Andy Simpson.  */
#define ASUS_P4SGX_WAKEUP_DAY         88
#define ASUS_P4SGX_CHECKSUM_REG      122

/* MSI K8T Neo-V.  */
#define MSI_K8T_NEO_WAKEUP_HOUR	      96 /* binary */
#define MSI_K8T_NEO_WAKEUP_MIN	      98 /* binary */
#define MSI_K8T_NEO_WAKEUP_SEC	      99 /* binary, sec|192 */
#define MSI_K8T_NEO_WAKEUP_DAY1	      95 /* binary, day|128 */
#define MSI_K8T_NEO_WAKEUP_DAY2	     125 /* BCD */

/**************************** BR Added the November 18th 2006 **************/
/* GIGABYTE GA-8I945GMF */
#define GA_8I945GMF_WAKEUP_DAY          85
#define GA_8I945GMF_WAKEUP_HOUR         86
#define GA_8I945GMF_WAKEUP_MIN		87
#define GA_8I945GMF_WAKEUP_SEC		88
#define GA_8I945GMF_CHECKSUM_REG	126

/* ASRock ConRoe945G.  */
#define ASROCK_CR945G_MIN  111 /* binary, min*2|128 */
#define ASROCK_CR945G_SEC  113
#define ASROCK_CR945G_DAY  114
#define ASROCK_CR945G_HOUR 116

/* Register values.  */
#define RTC_SET 0x80		/* disable updates for clock setting */
#define RTC_PIE 0x40		/* periodic interrupt enable */
#define RTC_AIE 0x20		/* alarm interrupt enable */
#define RTC_UIE 0x10		/* update-finished interrupt enable */
#define RTC_SQWE 0x08		/* enable square-wave output */
#define RTC_DM_BINARY 0x04	/* all time/date values are BCD if clear */
#define RTC_24H 0x02		/* 24 hour mode - else hours bit 7 means pm */

#define _(s)    (s)             /* i18n left for later */
#define ADJPATH "/etc/adjtime"

/***************************************************************************/

/* Global variables.  */
int             use_dev_port = 0;
int             dev_port_fd;
unsigned short  clock_ctl_addr = 0x70;
unsigned short  clock_data_addr = 0x71;
int             use_bcd = 0;
int             use_utc = 0; /* Hardware clock is known to be in UTC.  */
int             debug = 0;
const char*     progname = "wakeup_clock";

/***************************************************************************/

/* CMOS-related routines, adapted from hwclock.c. */

static inline int BCD_TO_BIN(int val) { return ((val)&15) + ((val)>>4)*10; }
static inline int BIN_TO_BCD(int val) { return (((val)/10)<<4) + (val)%10; }

static int
i386_iopl(const int level) {
#if defined(__i386__) || defined(__alpha__)
    extern int iopl(const int lvl);
    return iopl(level);
#else
    return -2;
#endif
}

static inline
unsigned long cmos_read(unsigned long reg)
{
    if (use_dev_port) {
	unsigned char v = reg | 0x80;
	lseek(dev_port_fd, clock_ctl_addr, 0);
	write(dev_port_fd, &v, 1);
	lseek(dev_port_fd, clock_data_addr, 0);
	read(dev_port_fd, &v, 1);
	return v;
    } else {
	outb (reg, clock_ctl_addr);
	return inb (clock_data_addr);
    }
}

static inline
unsigned long cmos_write(unsigned long reg, unsigned long val)
{
    if (use_dev_port) {
	unsigned char v = reg | 0x80;
	lseek(dev_port_fd, clock_ctl_addr, 0);
	write(dev_port_fd, &v, 1);
	v = (val & 0xff);
	lseek(dev_port_fd, clock_data_addr, 0);
	write(dev_port_fd, &v, 1);
    } else {
	outb (reg, clock_ctl_addr);
	outb (val, clock_data_addr);
    }
    return 0;
}

/* write with checksum update */
static inline void
cmos_write_cksum(unsigned long reg, unsigned long val, int* cksum)
{
    unsigned long old = cmos_read(reg);
    cmos_write(reg, val);
    *cksum += (int)val - (int)old;
}

/* write with checksum update, while preserving old contents in mask */
static inline void
cmos_write_cksum_p(unsigned long reg, unsigned long mask,
		   unsigned long val, int* cksum)
{
    unsigned long old = cmos_read(reg);
    val |= old & mask;
    cmos_write(reg, val);
    *cksum += (int)val - (int)old;
}

static void
cmos_update_cksum8(unsigned long reg, int cksum)
{
    cmos_write(reg, cksum & 0xFF);
}

static void
cmos_update_cksum16(unsigned long msb_reg, unsigned long lsb_reg, int cksum)
{
    //fprintf(stderr, "* new cksum is %04X\n", cksum & 0xFFFF);
    cmos_write(lsb_reg, cksum & 0xFF);
    cmos_write(msb_reg, (cksum >> 8) & 0xFF);
}

static int
get_permissions_cmos(void)
{
    int rc;
    unsigned long ctrl;

    if (use_dev_port) {
	if ((dev_port_fd = open("/dev/port", O_RDWR)) < 0) {
	    int errsv = errno;
	    fprintf(stderr, _("Cannot open /dev/port: %s"), strerror(errsv));
	    rc = 1;
	} else
	    rc = 0;
    } else {
	rc = i386_iopl(3);
	if (rc == -2) {
	    fprintf(stderr, _("Failed to get permission.\n"));
	} else if (rc != 0) {
	    rc = errno;
	    fprintf(stderr, _("%s is unable to get I/O port access:  "
			      "the iopl(3) call failed.\n"), progname);
	    if(rc == EPERM && geteuid())
		fprintf(stderr, _("Probably you need root privileges.\n"));
	}
    }
    if (rc)
	return 1;
    ctrl = cmos_read(RTC_CONTROL);
    if (!(ctrl & RTC_DM_BINARY)) {
	use_bcd = 1;
	fprintf(stderr, _("Using BCD.\n"));
    }
    return 0;
}

/**************************************************************************/

/* Read and parse /etc/adjtime, only to get the use_utc flag from the
   third line.  */
static int
read_adjtime(void)
{
    FILE *adjfile;
    int rc;  /* local return code */
    struct stat statbuf;  /* We don't even use the contents of this. */
    char line1[81];           /* String: first line of adjtime file */
    char line2[81];           /* String: second line of adjtime file */
    char line3[81];           /* String: third line of adjtime file */

    rc = stat(ADJPATH, &statbuf);
    if (rc < 0 && errno == ENOENT) {
	return 0;
    }

    adjfile = fopen(ADJPATH, "r");   /* open file for reading */
    if (adjfile == NULL) {
	fprintf(stderr, _("cannot open file %s\n"), ADJPATH);
	return -1;
    }

    line1[0] = '\0';          /* In case fgets fails */
    fgets(line1, sizeof(line1), adjfile);
    line2[0] = '\0';          /* In case fgets fails */
    fgets(line2, sizeof(line2), adjfile);
    line3[0] = '\0';          /* In case fgets fails */
    fgets(line3, sizeof(line3), adjfile);
      
    fclose(adjfile);

    if (!strcmp(line3, "UTC\n"))
	use_utc = 1;
    else if (!strcmp(line3, "LOCAL\n"))
	use_utc = 0;
    else if (line3[0]) {
	fprintf(stderr,
		_("%s: Warning: unrecognized third line in adjtime file\n"),
		progname);
	fprintf(stderr, _("(Expected: `UTC' or `LOCAL' or nothing.)\n"));
    }

    return 0;
}

/* Interpret the value of the date string, which is something like
   "4/5 13:05:01".  In fact, it can be any of the myriad ASCII strings
   that specify a time which the "date" program can understand.  The
   specified time is in the local time zone.  Taken from hwclock.c.  */
static int
interpret_date_string(const char *date_opt, time_t * const time_p)
{
    FILE *date_child_fp;
    char date_command[256];
    char date_resp[256];
    const char magic[] = "wake_magic";
    int retcode;  /* our eventual return code */
    int rc;  /* local return code */

    if (date_opt == NULL) {
	fprintf(stderr, _("No date specified.\n"));
	return 14;
    }

    /* prevent overflow - a security risk */
    if (strlen(date_opt) > sizeof(date_command) - 64) {
	fprintf(stderr, _("date argument too long\n"));
	return 13;
    }

    /* Quotes in date_opt would ruin the date command we construct. */
    if (strchr(date_opt, '"') != NULL) {
	fprintf(stderr,
		_("date string invalid, contains quotation marks.\n"));
	return 12;
    }

    sprintf(date_command, "date --date=\"%s\" \"+%s%%s\"", date_opt, magic);
    if (debug)
	fprintf(stderr, _("Issuing date command:\n %s\n"), date_command);

    date_child_fp = popen(date_command, "r");
    if (date_child_fp == NULL) {
	fprintf(stderr, _("Unable to run 'date' program in /bin/sh shell. "
			  "popen() failed"));
	return 10;
    }

    date_resp[0] = '\0';  /* in case fgets fails */
    fgets(date_resp, sizeof(date_resp), date_child_fp);
    if (debug)
	fprintf(stderr, _("response from date command = %s\n"), date_resp);
    if (strncmp(date_resp, magic, sizeof(magic)-1) != 0) {
	fprintf(stderr, _("The date command issued by %s returned "
			  "unexpected results.\n"
			  "The command was:\n  %s\n"
			  "The response was:\n  %s\n"), 
		progname, date_command, date_resp);
	retcode = 8;
    } else {
	long seconds_since_epoch;
	rc = sscanf(date_resp + sizeof(magic)-1, "%ld", &seconds_since_epoch);
	if (rc < 1) {
	    fprintf(stderr, _("The date command issued by %s returned "
			      "unexpected results.\n"
			      "The command was:\n  %s\n"
			      "The response was:\n  %s\n"),
		    progname, date_command, date_resp);
	    retcode = 6;
	} else {
	    retcode = 0;
	    *time_p = seconds_since_epoch;
	    if (debug) 
		fprintf(stderr, _("date string \"%s\" -> "
				  "%ld seconds since 1969.\n"),
			date_opt, seconds_since_epoch);
	}
    }
    fclose(date_child_fp);

    return retcode;
}

/***************************************************************************/

/* Set up RTC alarm time, required on most boards.
   Maybe we should use a write to /proc/acpi/alarm instead..  */
static void
set_alarm_time(int hour, int min, int sec)
{
    if (hour >= 0)
	cmos_write(RTC_HOURS_ALARM,
		   (use_bcd && hour<24) ? BIN_TO_BCD(hour) : hour);
    if (min >= 0)
	cmos_write(RTC_MINUTES_ALARM,
		   (use_bcd && min<60) ? BIN_TO_BCD(min) : min);
    if (sec >= 0)
	cmos_write(RTC_SECONDS_ALARM,
		   (use_bcd && sec<60) ? BIN_TO_BCD(sec) : sec);
    if (0) {
	unsigned long ctrl = cmos_read(RTC_CONTROL);
	cmos_write(RTC_CONTROL, ctrl|RTC_AIE);
	cmos_read(RTC_INTR_FLAGS); /* see kernel sources */
    }
}

/* Write board-specific registers.  */
void
write_cmos_K7S5A(int day, int hour, int min, int sec)
{
    int checksum = (signed char)cmos_read(CMOS_CHECKSUM_REG_MSB)*256 +
	(int)cmos_read(CMOS_CHECKSUM_REG_LSB);

    set_alarm_time(hour, min, sec);
#if 0
    old = cmos_read(95);
    if (!(old & 128)) {
	printf("Wakeup not on, setting to on.\n");
	cmos_write_cksum(95, old|128, &checksum);
    }
#endif
    cmos_write(ECS_K7S5A_WAKEUP_DAY, use_bcd ? BIN_TO_BCD(day) : day);
    cmos_write_cksum(96, day, &checksum);
    if (hour >= 0) // hour
	cmos_write_cksum_p(97, ~31, hour, &checksum);
    if (min >= 0) // minute
	cmos_write_cksum_p(98, ~63, min, &checksum);
    if (sec >= 0) // second
	cmos_write_cksum_p(99, ~63, sec, &checksum);
    cmos_update_cksum16(CMOS_CHECKSUM_REG_MSB,
			CMOS_CHECKSUM_REG_LSB,
			checksum);
}

void
write_cmos_ASUS(int day, int hour, int min, int sec)
{
    int old;

    set_alarm_time(hour, min, sec);
    old = cmos_read(ASUS_WAKEUP_DAY);
    old &= ~0x3F;
    cmos_write(ASUS_WAKEUP_DAY, old | (use_bcd ? BIN_TO_BCD(day) : day));
}

void
write_cmos_MSI6119(int day, int hour, int min, int sec)
{
    int old, checksum = cmos_read(MSI_CHECKSUM_REG);

    cmos_write_cksum(MSI_WAKEUP_DAY, day, &checksum);
    if (hour >= 0) { // hour
	old = cmos_read(MSI_WAKEUP_HOUR);
	cmos_write_cksum(MSI_WAKEUP_HOUR, (old & ~31) | hour, &checksum);
    }
    if (min >= 0) { // minute
	old = cmos_read(MSI_WAKEUP_MIN);
	cmos_write_cksum(MSI_WAKEUP_MIN, (old & ~63) | min, &checksum);
    }
    if (sec >= 0) { // second
	old = cmos_read(MSI_WAKEUP_SEC);
	cmos_write_cksum(MSI_WAKEUP_SEC, (old & ~63) | sec, &checksum);
    }
    cmos_update_cksum8(MSI_CHECKSUM_REG, checksum);
    fprintf(stderr,
	    _("*** Warning: a reboot cycle is required on this board\n"));
}

void
write_cmos_ECS_K7VZA(int day, int hour, int min, int sec)
{
    int old, checksum = cmos_read(ECS_K7VZA_CHECKSUM_REG);

    cmos_write_cksum(ECS_K7VZA_WAKEUP_DAY, day, &checksum);
    if (hour >= 0) { // hour
	old = cmos_read(ECS_K7VZA_WAKEUP_HOUR);
	cmos_write_cksum(ECS_K7VZA_WAKEUP_HOUR, (old & ~31) | hour, &checksum);
    }
    if (min >= 0) { // minute
	old = cmos_read(ECS_K7VZA_WAKEUP_MIN);
	cmos_write_cksum(ECS_K7VZA_WAKEUP_MIN, (old & ~63) | min, &checksum);
    }
    if (sec >= 0) { // second
	old = cmos_read(ECS_K7VZA_WAKEUP_SEC);
	cmos_write_cksum(ECS_K7VZA_WAKEUP_SEC, (old & ~63) | sec, &checksum);
    }
    cmos_update_cksum8(ECS_K7VZA_CHECKSUM_REG, checksum);
    //set_alarm_time(hour, min, sec);
}

void
write_cmos_MSI_K7TTURBO2(int day, int hour, int min, int sec)
{
    int old, checksum = cmos_read(MSI_K7TTURBO2_CHECKSUM_REG);

    cmos_write_cksum(MSI_K7TTURBO2_WAKEUP_DAY, day, &checksum);
    if (hour >= 0) { // hour
	old = cmos_read(MSI_K7TTURBO2_WAKEUP_HOUR);
	cmos_write_cksum(MSI_K7TTURBO2_WAKEUP_HOUR, (old & ~31) | hour, &checksum);
    }
    if (min >= 0) { // minute
	old = cmos_read(MSI_K7TTURBO2_WAKEUP_MIN);
	cmos_write_cksum(MSI_K7TTURBO2_WAKEUP_MIN, (old & ~63) | min, &checksum);
    }
    if (sec >= 0) { // second
	old = cmos_read(MSI_K7TTURBO2_WAKEUP_SEC);
	cmos_write_cksum(MSI_K7TTURBO2_WAKEUP_SEC, (old & ~63) | sec, &checksum);
    }
    cmos_update_cksum8(MSI_K7TTURBO2_CHECKSUM_REG, checksum);
    //set_alarm_time(hour, min, sec);
}

void
write_cmos_K7VT2(int day, int hour, int min, int sec)
{
    int old, cksum;

    cksum = (signed char)cmos_read(CMOS_CHECKSUM_REG_MSB)*256 +
	(int)cmos_read(CMOS_CHECKSUM_REG_LSB);
    //fprintf(stderr, "* old cksum is %04X\n", cksum);

    old = cmos_read(ASROCK_K7VT2_WAKEUP_DAY1);
    cmos_write_cksum(ASROCK_K7VT2_WAKEUP_DAY1, (day*8) | (old & 0x7), &cksum);
    cmos_write(ASROCK_K7VT2_WAKEUP_DAY2, BIN_TO_BCD(day));
    if (hour >= 0) { // hour
	old = cmos_read(ASROCK_K7VT2_WAKEUP_HOUR);
	cmos_write_cksum(ASROCK_K7VT2_WAKEUP_HOUR, (old & ~31) | hour, &cksum);
    }
    if (min >= 0) { // minute
	old = cmos_read(ASROCK_K7VT2_WAKEUP_MIN);
	cmos_write_cksum(ASROCK_K7VT2_WAKEUP_MIN, (old & ~63) | min, &cksum);
    }
    if (sec >= 0) { // second
	old = cmos_read(ASROCK_K7VT2_WAKEUP_SEC);
	cmos_write_cksum(ASROCK_K7VT2_WAKEUP_SEC, (old & ~63) | sec, &cksum);
    }
    cmos_update_cksum16(CMOS_CHECKSUM_REG_MSB,
			CMOS_CHECKSUM_REG_LSB,
			cksum);
    set_alarm_time(hour, min, sec);
    fprintf(stderr,
	    _("*** Warning: a reboot cycle is required on this board\n"));
}

void
write_cmos_K7VT6(int day, int hour, int min, int sec)
{
    int old, cksum;

    cksum = (signed char)cmos_read(CMOS_CHECKSUM_REG_MSB)*256 +
	(int)cmos_read(CMOS_CHECKSUM_REG_LSB);
    //fprintf(stderr, "* old cksum is %04X\n", cksum);

    cmos_write_cksum(ASROCK_K7VT6_WAKEUP_DAY1, day, &cksum);
    cmos_write(ASROCK_K7VT6_WAKEUP_DAY2, BIN_TO_BCD(day));
    if (hour >= 0) { // hour
	old = cmos_read(ASROCK_K7VT6_WAKEUP_HOUR);
	cmos_write_cksum(ASROCK_K7VT6_WAKEUP_HOUR, (old & ~31) | hour, &cksum);
    }
    if (min >= 0) { // minute
	old = cmos_read(ASROCK_K7VT6_WAKEUP_MIN);
	cmos_write_cksum(ASROCK_K7VT6_WAKEUP_MIN, (old & ~63) | min, &cksum);
    }
    if (sec >= 0) { // second
	old = cmos_read(ASROCK_K7VT6_WAKEUP_SEC);
	cmos_write_cksum(ASROCK_K7VT6_WAKEUP_SEC, (old & ~63) | sec, &cksum);
    }
    cmos_update_cksum16(CMOS_CHECKSUM_REG_MSB,
			CMOS_CHECKSUM_REG_LSB,
			cksum);
    set_alarm_time(hour, min, sec);
    fprintf(stderr,
	    _("*** Warning: a reboot cycle is required on this board\n"));
}

void
write_cmos_K7S8X(int day, int hour, int min, int sec)
{
    int cksum, old;

    cksum = (signed char)cmos_read(CMOS_CHECKSUM_REG_MSB)*256 +
	(int)cmos_read(CMOS_CHECKSUM_REG_LSB);

    old = cmos_read(97);
    cmos_write_cksum(97, (day*8) | (old & 0x7), &cksum);
    cmos_write(126, BIN_TO_BCD(day));
    if (hour >= 0) { // hour
	cmos_write_cksum(98, hour, &cksum);
    }
    if (min >= 0) { // minute
	cmos_write_cksum(99, min, &cksum);
    }
    if (sec >= 0) { // second
	cmos_write_cksum(100, sec, &cksum);
    }
    cmos_update_cksum16(CMOS_CHECKSUM_REG_MSB,
			CMOS_CHECKSUM_REG_LSB,
			cksum);
    set_alarm_time(hour, min, sec);
    fprintf(stderr,
	    _("*** Warning: a reboot cycle is required on this board\n"));
}

void
write_cmos_ASUS_A7V8X(int day, int hour, int min, int sec)
{
    int old1, old2, checksum;
   
    checksum = (signed char)cmos_read(ASUS_A7V8X_X_CHECKSUM_REG1)*256 +
	(int)cmos_read(ASUS_A7V8X_X_CHECKSUM_REG2);

    old1 = cmos_read(ASUS_A7V8X_X_WAKEUP_DAY1);
    old2 = cmos_read(ASUS_A7V8X_X_WAKEUP_DAY2);

    cmos_write_cksum(ASUS_A7V8X_X_WAKEUP_DAY1,
		     (old1 & ~0xC0) | ((day << 6) & 0xFF), &checksum);
    cmos_write_cksum(ASUS_A7V8X_X_WAKEUP_DAY2,
		     (old2 & ~0x07) | ((day >> 2) & 0xFF), &checksum); 

    set_alarm_time(hour, min, sec);
       
    cmos_update_cksum16(ASUS_A7V8X_X_CHECKSUM_REG1,
			ASUS_A7V8X_X_CHECKSUM_REG2, checksum);
}

void
write_cmos_VIA_EPIA_M(int day, int hour, int min, int sec)
{
    int old, checksum = cmos_read(VIA_EPIA_M_CHECKSUM_REG);

    cmos_write_cksum(VIA_EPIA_M_WAKEUP_DAY, day, &checksum);
    if (hour >= 0) { // hour
	old = cmos_read(VIA_EPIA_M_WAKEUP_HOUR);
	cmos_write_cksum(VIA_EPIA_M_WAKEUP_HOUR, (old & ~31) | hour, &checksum);
    }
    if (min >= 0) { // minute
	old = cmos_read(VIA_EPIA_M_WAKEUP_MIN);
	cmos_write_cksum(VIA_EPIA_M_WAKEUP_MIN, (old & ~63) | min, &checksum);
    }
    if (sec >= 0) { // second
	old = cmos_read(VIA_EPIA_M_WAKEUP_SEC);
	cmos_write_cksum(VIA_EPIA_M_WAKEUP_SEC, (old & ~63) | sec, &checksum);
    }
    cmos_update_cksum8(VIA_EPIA_M_CHECKSUM_REG, checksum);
    //set_alarm_time(hour, min, sec);
}

void
write_cmos_ASUS_P4SGX(int day, int hour, int min, int sec)
{
    int old, checksum;
   
    checksum = cmos_read(ASUS_P4SGX_CHECKSUM_REG);
    old = cmos_read(ASUS_P4SGX_WAKEUP_DAY);

    set_alarm_time(hour, min, sec);  /* doesn't touch checksum*/
    cmos_write_cksum(ASUS_P4SGX_WAKEUP_DAY,
		     (old & ~0x7C) | ((day << 2) & 0xFF), &checksum);       
    cmos_update_cksum8(ASUS_P4SGX_CHECKSUM_REG, checksum);
}

void
write_cmos_MSI_K8T_NEO(int day, int hour, int min, int sec)
{
    int checksum = (signed char)cmos_read(CMOS_CHECKSUM_REG_MSB)*256 +
	(int)cmos_read(CMOS_CHECKSUM_REG_LSB);
    
    cmos_write_cksum_p(MSI_K8T_NEO_WAKEUP_DAY1, ~31, day, &checksum);
    cmos_write_cksum(MSI_K8T_NEO_WAKEUP_HOUR, hour, &checksum);
    cmos_write_cksum(MSI_K8T_NEO_WAKEUP_MIN, min, &checksum);
    cmos_write_cksum_p(MSI_K8T_NEO_WAKEUP_SEC, ~63, sec, &checksum);
    cmos_write(MSI_K8T_NEO_WAKEUP_DAY2, use_bcd ? BIN_TO_BCD(day) : day);
    cmos_update_cksum16(CMOS_CHECKSUM_REG_MSB,
			CMOS_CHECKSUM_REG_LSB, checksum);
    set_alarm_time(hour, min, sec);
}

/*************************BR added the November 18th 2006 ******************/
void
write_cmos_GA_8I945GMF(int day, int hour, int min, int sec)
{
    int old, checksum = cmos_read(GA_8I945GMF_CHECKSUM_REG);

    cmos_write_cksum(GA_8I945GMF_WAKEUP_DAY, day, &checksum);
    if (hour >= 0) { // hour
	old = cmos_read(GA_8I945GMF_WAKEUP_HOUR);
	cmos_write_cksum(GA_8I945GMF_WAKEUP_HOUR, (old & ~31) | hour, &checksum);
    }
    if (min >= 0) { // minute
	old = cmos_read(GA_8I945GMF_WAKEUP_MIN);
	cmos_write_cksum(GA_8I945GMF_WAKEUP_MIN, (old & ~63) | min, &checksum);
    }
    if (sec >= 0) { // second
	old = cmos_read(GA_8I945GMF_WAKEUP_SEC);
	cmos_write_cksum(GA_8I945GMF_WAKEUP_SEC, (old & ~63) | sec, &checksum);
    }
    cmos_update_cksum8(GA_8I945GMF_CHECKSUM_REG, checksum);
}

void
write_cmos_ASRock_CR945G(int day, int hour, int min, int sec)
{
    int checksum = (signed char)cmos_read(CMOS_CHECKSUM_REG_MSB)*256 +
	(int)cmos_read(CMOS_CHECKSUM_REG_LSB);
    
    cmos_write_cksum_p(ASROCK_CR945G_DAY,   ~31, day, &checksum);
    cmos_write_cksum_p(ASROCK_CR945G_HOUR,  ~31, hour, &checksum);
    cmos_write_cksum_p(ASROCK_CR945G_MIN,  ~126, min*2, &checksum);
    cmos_write_cksum_p(ASROCK_CR945G_SEC,   ~63, sec, &checksum);
    cmos_update_cksum16(CMOS_CHECKSUM_REG_MSB,
			CMOS_CHECKSUM_REG_LSB, checksum);
}

/***************************************************************************/

static int board_type = -1;

const struct board_entry {
    const char* sig;
    long offset;
    const char* name;
    void (*wbs)(int day, int hour, int min, int sec);
} board_tab[] = {
    {"ASUS P5A",  0x000FE000, "ASUS P5A",  write_cmos_ASUS},
    {"ASUS P2B",  0x000F5000, "ASUS P2B",  write_cmos_ASUS},
    {"ASUS P3B",  0x000F5000, "ASUS P3B",  write_cmos_ASUS},
    {"K7VT2 BIOS",0x000FF000, "ASRock K7VT2", write_cmos_K7VT2},
    {"K7VT6 BIOS ",0x000FF000, "ASRock K7VT6", write_cmos_K7VT6},
    {"K7S5A ",    0x000FF000, "ECS K7S5A", write_cmos_K7S5A},
    {"K7VZA ",    0x000FC000, "ECS K7VZA", write_cmos_ECS_K7VZA},
    {"W6119MS V", 0x000FE000, "MSI 6119",  write_cmos_MSI6119},
    {"W6330VMS V", 0x000FE000, "MSI K7T2",  write_cmos_MSI_K7TTURBO2},
    {"K7S8X BIOS ",0x000FF000, "ASRock K7S8X", write_cmos_K7S8X},
    {"ASUS A7V8X", 0x000FE000, "ASUS A7V8X", write_cmos_ASUS_A7V8X},
    {"ASUS P4SGX", 0x000F5000, "ASUS P4SGX", write_cmos_ASUS_P4SGX},
    {"EPIA-M",    0x000FE000, "EPIA-M", write_cmos_VIA_EPIA_M},
    {"A7032VMS V",0x000FF000, "MSI K8T Neo", write_cmos_MSI_K8T_NEO},
    {"IS7-E2",    0x012C2E00, "ABIT IS7-E2",  write_cmos_MSI6119},
    {"GA-8ISXT",  0x000F0174, "GA-8ISXT",  write_cmos_MSI6119},
    {"8I945GMF",  0x000F018E, "8I945GMF",  write_cmos_GA_8I945GMF},

    {"ConRoe945G-DVI BIOS P", 0x000FF000,
     "ASRock ConRoe945G", write_cmos_ASRock_CR945G},
    {NULL,        0x0,        NULL,        NULL}
};

#define BUF_SIZE 1024

static int
find_signature(const char* sig, long start, long end)
{
    int fd, i;
    char buf[BUF_SIZE*2];
    unsigned long res = 0;
    size_t len = strlen(sig);

    if (len==0 || len>BUF_SIZE)
	return -100;
    fd = open("/dev/mem", O_RDONLY);
    if (fd < 0)
	return -1;
    memset(buf, 0, BUF_SIZE);
    if (lseek(fd, start, SEEK_SET) == -1) {
	close(fd);
	return -2;
    }
    while (start < end) {
	if (read(fd, buf+BUF_SIZE, BUF_SIZE) < BUF_SIZE)
	    break;
	for (i=0; i<(BUF_SIZE*2)-len; ++i)
	    if (buf[i] == sig[0])
		if (!strncmp(&buf[i+1], sig+1, len-1)) {
		    res = (unsigned long)start + i - BUF_SIZE;
		    if (debug)
			fprintf(stderr,
				"Found at %08lX: %.100s\n", res, &buf[i]);
		    goto done;
		}
	memcpy(buf, buf+BUF_SIZE, BUF_SIZE);
	start += BUF_SIZE;
    }
 done:
    close(fd);
    return res != 0 ? 0 : -2;
}

#undef BUF_SIZE

int
detect_board(void)
{
    int i;

    for (i=0; board_tab[i].sig; ++i) {
	/* end used to be fixed at 0x100000, now more dynamic.. */
	long end = (board_tab[i].offset + 0x2000) & ~0x1FFFl;
	//fprintf(stderr, "* end=%08lx\n", end);
	if (!find_signature(board_tab[i].sig,
			    board_tab[i].offset,
			    end)) {
	    board_type = i;
	    return 0;
	}
    }
    return -1;
}

/***************************************************************************/

int
main(int argc, char* argv[])
{
    int r;
    int did_set = 0;
    time_t t_wakeup;
    struct tm tm_wakeup;

    if (argc < 2) {
	fprintf(stderr, _("Usage  : %s date_string\n"), argv[0]);
	fprintf(stderr, _("  note : date_string interpreted in local time\n"));
	fprintf(stderr, _("Example: %s \"4/5 14:15\"\n"), argv[0]);
	exit(1);
    }
    /* Parse options */
    if (!strcmp(argv[1], "-p")) {
	use_dev_port = 1;
	++argv; --argc;
    }
    if (!strcmp(argv[1], "-d")) {
	if (get_permissions_cmos())
	    return 1;
	/* Dump CMOS state.  */
	for (r=0; r<128; ++r) {
	    int val = cmos_read(r);
	    printf("%3d %03d BCD=%03d\n", r, val, BCD_TO_BIN(val));
	}
	return 0;
    }

    /* Set the wakeup date/time.  */
    read_adjtime();
    if (interpret_date_string(argv[1], &t_wakeup)) {
	fprintf(stderr, _("Cannot parse date string '%s'.\n"), argv[1]);
	return 1;
    }
    tm_wakeup = use_utc ? *gmtime(&t_wakeup) : *localtime(&t_wakeup);
    if (debug<=1 && !get_permissions_cmos()) {
	if (detect_board()) {
	    fprintf(stderr, _("Can't detect board type!\n"));
	    board_type = -1;
	} else {
	    fprintf(stderr, _("Detected board type: %s\n"),
		    board_tab[board_type].name);

	    if (debug == 0) {
		/* Write board-specific registers. */
		if (board_tab[board_type].wbs)
		    (board_tab[board_type].wbs)(tm_wakeup.tm_mday,
						tm_wakeup.tm_hour,
						tm_wakeup.tm_min,
						tm_wakeup.tm_sec);
		did_set = 1;
	    }
	}
    }
    printf(did_set ? _("Wakeup date set:\n") : _("Wakeup date not set:\n"));
    printf(_("day=%d hour=%d min=%d sec=%d\n"),
	   tm_wakeup.tm_mday,
	   tm_wakeup.tm_hour,
	   tm_wakeup.tm_min,
	   tm_wakeup.tm_sec);
    return 0;
}

/*
 * Local variables:
 * tab-width: 8
 * c-basic-offset: 4
 * compile-command: "gcc -O -Wall wakeup_clock.c -o wakeup_clock"
 * End:
 */
