patch-2.4.10 linux/drivers/acpi/ospm/system/sm_osl.c

Next file: linux/drivers/acpi/ospm/thermal/tz.c
Previous file: linux/drivers/acpi/ospm/system/sm.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.9/linux/drivers/acpi/ospm/system/sm_osl.c linux/drivers/acpi/ospm/system/sm_osl.c
@@ -1,7 +1,7 @@
 /******************************************************************************
  *
  * Module Name: sm_osl.c
- *   $Revision: 10 $
+ *   $Revision: 16 $
  *
  *****************************************************************************/
 
@@ -31,6 +31,10 @@
 #include <linux/proc_fs.h>
 #include <linux/pm.h>
 #include <asm/uaccess.h>
+#include <linux/acpi.h>
+#include <asm/io.h>
+#include <linux/mc146818rtc.h>
+#include <linux/delay.h>
 
 #include <acpi.h>
 #include "sm.h"
@@ -38,6 +42,7 @@
 
 MODULE_AUTHOR("Andrew Grover");
 MODULE_DESCRIPTION("ACPI Component Architecture (CA) - ACPI System Driver");
+MODULE_LICENSE("GPL");
 
 
 #define SM_PROC_INFO		"info"
@@ -53,6 +58,92 @@
 	read:		sm_osl_read_dsdt,
 };
 
+static acpi_status sm_osl_suspend(u32 state);
+
+struct proc_dir_entry *bm_proc_sleep;
+struct proc_dir_entry *bm_proc_alarm;
+struct proc_dir_entry *bm_proc_gpe;
+
+static int
+sm_osl_proc_read_sleep (
+        char                    *page,
+        char                    **start,
+        off_t                   off,
+        int                     count,
+        int                     *eof,
+        void                    *context)
+{
+	SM_CONTEXT    *system = (SM_CONTEXT*) context;
+	char          *str = page;
+	int           len;
+	int           i;
+
+	if (!system)
+               goto end;
+
+	if (off != 0)
+               goto end;
+
+	for (i = 0; i <= ACPI_S5; i++) {
+		if (system->states[i])
+			str += sprintf(str,"S%d ", i);
+	}
+
+	str += sprintf(str, "\n");
+
+end:
+
+	len = (str - page);
+	if (len < (off + count))
+		*eof = 1;
+
+	*start = page + off;
+	len -= off;
+
+	if (len > count)
+		len = count;
+
+	if (len < 0)
+		len = 0;
+
+	return (len);
+}
+
+int sm_osl_proc_write_sleep (struct file *file,
+			     const char *buffer,
+			     unsigned long count,
+			     void *data)
+{
+	SM_CONTEXT    *system = (SM_CONTEXT*) data;
+	char          str[10];
+	char          *strend;
+	unsigned long value;
+	
+	if (count > (sizeof(str) - 1))
+		return -EINVAL;
+	
+	if (copy_from_user(str,buffer,count))
+		return -EFAULT;
+	
+	str[count] = '\0';
+	
+	value = simple_strtoul(str,&strend,0);
+	if (str == strend)
+		return -EINVAL;
+	
+	if (value == 0 || value >= ACPI_S5)
+		return -EINVAL;
+	
+	/*
+	 * make sure that the sleep state is supported
+	 */
+	if (system->states[value] != TRUE)
+		return -EINVAL;
+	
+	sm_osl_suspend(value);
+	
+	return (count);
+}
 
 
 /****************************************************************************
@@ -70,19 +161,19 @@
 	int 			*eof,
 	void			*context)
 {
-	ACPI_STATUS		status = AE_OK;
+	acpi_status		status = AE_OK;
 	SM_CONTEXT		*system = NULL;
 	char			*p = page;
 	int			len;
-	ACPI_SYSTEM_INFO	system_info;
-	ACPI_BUFFER		buffer;
+	acpi_system_info	system_info;
+	acpi_buffer		buffer;
 	u32			i = 0;
 
 	if (!context) {
 		goto end;
 	}
 
-	system = (SM_CONTEXT*)context;
+	system = (SM_CONTEXT*) context;
 
 	/* don't get status more than once for a single proc read */
 	if (off != 0) {
@@ -100,7 +191,7 @@
 		p += sprintf(p, "ACPI-CA Version:         unknown\n");
 	}
 	else {
-		p += sprintf(p, "ACPI-CA Version:         %x\n", 
+		p += sprintf(p, "ACPI-CA Version:         %x\n",
 			system_info.acpi_ca_version);
 	}
 
@@ -131,12 +222,12 @@
 
 static ssize_t
 sm_osl_read_dsdt(
-	struct file		*file, 
-	char			*buf, 
-	size_t			count, 
+	struct file		*file,
+	char			*buf,
+	size_t			count,
 	loff_t			*ppos)
 {
-	ACPI_BUFFER		acpi_buf;
+	acpi_buffer		acpi_buf;
 	void			*data;
 	size_t			size = 0;
 
@@ -178,6 +269,459 @@
 	return size;
 }
 
+static int
+sm_osl_proc_read_alarm (
+	char                    *page,
+	char                    **start,
+	off_t                   off,
+	int                     count,
+	int                     *eof,
+	void                    *context)
+{
+	char *str = page;
+	int len;
+	u32 sec,min,hr;
+	u32 day,mo,yr;
+
+	if (off != 0) goto out;
+
+	spin_lock(&rtc_lock);
+	sec = CMOS_READ(RTC_SECONDS_ALARM);
+	min = CMOS_READ(RTC_MINUTES_ALARM);
+	hr = CMOS_READ(RTC_HOURS_ALARM);
+
+#if 0
+	/* if I ever get an FACP with proper values, maybe I'll enable this code */
+	if (acpi_gbl_FADT->day_alrm)
+		day = CMOS_READ(acpi_gbl_FADT->day_alrm);
+	else
+		day =  CMOS_READ(RTC_DAY_OF_MONTH);
+	if (acpi_gbl_FADT->mon_alrm)
+		mo = CMOS_READ(acpi_gbl_FADT->mon_alrm);
+	else
+		mo = CMOS_READ(RTC_MONTH);;
+	if (acpi_gbl_FADT->century)
+		yr = CMOS_READ(acpi_gbl_FADT->century) * 100 + CMOS_READ(RTC_YEAR);
+	else
+		yr = CMOS_READ(RTC_YEAR);
+#else
+	day = CMOS_READ(RTC_DAY_OF_MONTH);
+	mo = CMOS_READ(RTC_MONTH);
+	yr = CMOS_READ(RTC_YEAR);
+#endif
+	spin_unlock(&rtc_lock);
+
+	BCD_TO_BIN(sec);
+	BCD_TO_BIN(min);
+	BCD_TO_BIN(hr);
+	BCD_TO_BIN(day);
+	BCD_TO_BIN(mo);
+	BCD_TO_BIN(yr);
+
+	str += sprintf(str,"%4.4u-",yr);
+
+	str += (mo > 12) ?
+		sprintf(str,"**-") :
+		sprintf(str,"%2.2u-",mo);
+
+	str += (day > 31) ?
+		sprintf(str,"** ") :
+		sprintf(str,"%2.2u ",day);
+
+	str += (hr > 23) ?
+		sprintf(str,"**:") :
+		sprintf(str,"%2.2u:",hr);
+
+	str += (min > 59) ?
+		sprintf(str,"**:") :
+		sprintf(str,"%2.2u:",min);
+
+	str += (sec > 59) ?
+		sprintf(str,"**\n") :
+		sprintf(str,"%2.2u\n",sec);
+
+ out:
+	len = str - page;
+
+	if (len < count) *eof = 1;
+	else if (len > count) len = count;
+
+	if (len < 0) len = 0;
+
+	*start = page;
+
+	return len;
+}
+
+static int get_date_field(char **str, u32 *value)
+{
+	char *next,*strend;
+	int error = -EINVAL;
+
+	/* try to find delimeter, only to insert null;
+	 *  the end of string won't have one, but is still valid
+	 */
+	next = strpbrk(*str,"- :");
+	if (next) *next++ = '\0';
+
+	*value = simple_strtoul(*str,&strend,10);
+
+	/* signal success if we got a good digit */
+	if (strend != *str) error = 0;
+
+	if (next) *str = next;
+	return error;
+}
+
+
+
+int sm_osl_proc_write_alarm (
+	struct file *file,
+	const char *buffer,
+	unsigned long count,
+	void *data)
+{
+	char buf[30];
+	char *str = buf;
+	u32 sec,min,hr;
+	u32 day,mo,yr;
+	int adjust = 0;
+	unsigned char rtc_control;
+	int error = -EINVAL;
+
+	if (count > sizeof(buf) - 1) return -EINVAL;
+	
+	if (copy_from_user(str,buffer,count)) return -EFAULT;
+
+	str[count] = '\0';
+	/* check for time adjustment */
+	if (str[0] == '+') {
+		str++;
+		adjust = 1;
+	}
+
+	if ((error = get_date_field(&str,&yr)))  goto out;
+	if ((error = get_date_field(&str,&mo)))  goto out;
+	if ((error = get_date_field(&str,&day))) goto out;
+	if ((error = get_date_field(&str,&hr)))  goto out;
+	if ((error = get_date_field(&str,&min))) goto out;
+	if ((error = get_date_field(&str,&sec))) goto out;
+
+
+	if (sec > 59) {
+		min += 1;
+		sec -= 60;
+	}
+	if (min > 59) {
+		hr += 1;
+		min -= 60;
+	} 
+	if (hr > 23) {
+		day += 1;
+		hr -= 24;
+	}
+	if (day > 31) { 
+		mo += 1;
+		day -= 31;
+	}
+	if (mo > 12) {
+		yr += 1;
+		mo -= 12;
+	}
+
+	spin_lock_irq(&rtc_lock);
+	rtc_control = CMOS_READ(RTC_CONTROL);
+	if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+		BIN_TO_BCD(yr);
+		BIN_TO_BCD(mo);
+		BIN_TO_BCD(day);
+		BIN_TO_BCD(hr);
+		BIN_TO_BCD(min);
+		BIN_TO_BCD(sec);
+	}
+
+	if (adjust) {
+		yr  += CMOS_READ(RTC_YEAR);
+		mo  += CMOS_READ(RTC_MONTH);
+		day += CMOS_READ(RTC_DAY_OF_MONTH);
+		hr  += CMOS_READ(RTC_HOURS);
+		min += CMOS_READ(RTC_MINUTES);
+		sec += CMOS_READ(RTC_SECONDS);
+	}
+	spin_unlock_irq(&rtc_lock);
+
+	if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+		BCD_TO_BIN(yr);
+		BCD_TO_BIN(mo);
+		BCD_TO_BIN(day);
+		BCD_TO_BIN(hr);
+		BCD_TO_BIN(min);
+		BCD_TO_BIN(sec);
+	}
+
+	if (sec > 59) {
+		min++;
+		sec -= 60;
+	}
+	if (min > 59) {
+		hr++;
+		min -= 60;
+	}
+	if (hr > 23) {
+		day++;
+		hr -= 24;
+	}
+	if (day > 31) {
+		mo++;
+		day -= 31;
+	}
+	if (mo > 12) {
+		yr++;
+		mo -= 12;
+	}
+	if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+		BIN_TO_BCD(yr);
+		BIN_TO_BCD(mo);
+		BIN_TO_BCD(day);
+		BIN_TO_BCD(hr);
+		BIN_TO_BCD(min);
+		BIN_TO_BCD(sec);
+	}
+
+	spin_lock_irq(&rtc_lock);
+	/* write the fields the rtc knows about */
+	CMOS_WRITE(hr,RTC_HOURS_ALARM);
+	CMOS_WRITE(min,RTC_MINUTES_ALARM);
+	CMOS_WRITE(sec,RTC_SECONDS_ALARM);
+
+	/* If the system supports an enhanced alarm, it will have non-zero
+	 * offsets into the CMOS RAM here.
+	 * Which for some reason are pointing to the RTC area of memory.
+	 */
+#if 0
+	if (acpi_gbl_FADT->day_alrm) CMOS_WRITE(day,acpi_gbl_FADT->day_alrm);
+	if (acpi_gbl_FADT->mon_alrm) CMOS_WRITE(mo,acpi_gbl_FADT->mon_alrm);
+	if (acpi_gbl_FADT->century)  CMOS_WRITE(yr / 100,acpi_gbl_FADT->century);
+#endif
+	/* enable the rtc alarm interrupt */
+	if (!(rtc_control & RTC_AIE)) {
+		rtc_control |= RTC_AIE;
+		CMOS_WRITE(rtc_control,RTC_CONTROL);
+		CMOS_READ(RTC_INTR_FLAGS);
+	}
+
+	/* unlock the lock on the rtc now that we're done with it */
+	spin_unlock_irq(&rtc_lock);
+
+	acpi_hw_register_bit_access(ACPI_WRITE,ACPI_MTX_LOCK, RTC_EN, 1);
+
+	file->f_pos += count;
+
+	error = 0;
+ out:
+	return error ? error : count;
+}
+
+static int 
+sm_osl_proc_read_gpe(
+	char                    *page,
+	char                    **start,
+	off_t                   off,
+	int                     count,
+	int                     *eof,
+	void                    *context)
+{
+	char *str = page;
+	int size;
+	int length;
+	int i;
+	u32 addr,data;
+	
+	if (off) goto out;
+
+	if (acpi_gbl_FADT->V1_gpe0blk) {
+		length = acpi_gbl_FADT->gpe0blk_len / 2;
+
+		str += sprintf(str,"GPE0: ");
+
+		for (i = length; i > 0; i--) {
+			addr = GPE0_EN_BLOCK | (i - 1);
+			data = acpi_hw_register_read(ACPI_MTX_LOCK,addr);
+			str += sprintf(str,"%2.2x ",data);
+		}
+		str += sprintf(str,"\n");
+
+		str += sprintf(str,"Status: ");
+		for (i = length; i > 0; i--) {
+			addr = GPE0_STS_BLOCK | (i - 1);
+			data = acpi_hw_register_read(ACPI_MTX_LOCK,addr);
+			str += sprintf(str,"%2.2x ",data);
+		}
+		str += sprintf(str,"\n");
+	}
+
+	if (acpi_gbl_FADT->V1_gpe1_blk) {
+		length = acpi_gbl_FADT->gpe1_blk_len / 2;
+
+
+		str += sprintf(str,"GPE1: ");
+		for (i = length; i > 0; i--) {
+			addr = GPE1_EN_BLOCK | (i - 1);
+			data = acpi_hw_register_read(ACPI_MTX_LOCK,addr);
+			str += sprintf(str,"%2.2x",data);
+		}
+		str += sprintf(str,"\n");
+
+		str += sprintf(str,"Status: ");
+		for (i = length; i > 0; i--) {
+			addr = GPE1_STS_BLOCK | (i - 1);
+			data = acpi_hw_register_read(ACPI_MTX_LOCK,addr);
+			str += sprintf(str,"%2.2x",data);
+		}
+		str += sprintf(str,"\n");
+	}
+ out:
+	size = str - page;
+	if (size < count) *eof = 1;
+	else if (size > count) size = count;
+
+	if (size < 0) size = 0;
+	*start = page;
+
+	return size;
+}
+
+static int
+sm_osl_proc_write_gpe (
+	struct file *file,
+	const char *buffer,
+	unsigned long count,
+	void *data)
+{
+	char buf[256];
+	char *str = buf;
+	char *next;
+	int error = -EINVAL;
+	u32 addr,value = 0;
+
+	if (count > sizeof(buf) + 1) return -EINVAL;
+	
+	if (copy_from_user(str,buffer,count)) return -EFAULT;
+
+	str[count] = '\0';
+
+	/* set addr to which block to refer to */
+	if (!strncmp(str,"GPE0 ",5))      addr = GPE0_EN_BLOCK;
+	else if (!strncmp(str,"GPE1 ",5)) addr = GPE1_EN_BLOCK;
+	else goto out;
+
+	str += 5;
+
+	/* set low order bits to index of bit to set */
+	addr |= simple_strtoul(str,&next,0);
+	if (next == str) goto out;
+
+	if (next) {
+		str = ++next;
+		value = simple_strtoul(str,&next,0);
+		if (next == str) value = 1;
+	}
+
+	value = acpi_hw_register_bit_access(ACPI_WRITE,ACPI_MTX_LOCK,addr,(value ? 1 : 0));
+
+	error = 0;
+ out:
+	return error ? error : count;
+}
+
+
+/****************************************************************************
+ *
+ * FUNCTION:    sm_osl_suspend
+ *
+ * PARAMETERS:  %state: Sleep state to enter. Assumed that caller has filtered
+ *              out bogus values, so it's one of S1, S2, S3 or S4
+ *
+ * RETURN:      ACPI_STATUS, whether or not we successfully entered and
+ *              exited sleep.
+ *
+ * DESCRIPTION:
+ * This function is the meat of the sleep routine, as far as the ACPI-CA is
+ * concerned.
+ *
+ * See Chapter 9 of the ACPI 2.0 spec for details concerning the methodology here.
+ *
+ * It will do the following things:
+ * - Call arch-specific routines to save the processor and kernel state
+ * - Call acpi_enter_sleep_state to actually go to sleep
+ * ....
+ * When we wake back up, we will:
+ * - Restore the processor and kernel state
+ * - Return to the user
+ *
+ * By having this routine in here, it hides it from every part of the CA,
+ * so it can remain OS-independent. The only function that calls this is
+ * sm_proc_write_sleep, which gets the sleep state to enter from the user.
+ *
+ ****************************************************************************/
+static acpi_status
+sm_osl_suspend(u32 state)
+{
+	acpi_status status = AE_ERROR;
+	unsigned long wakeup_address;
+
+	/* get out if state is invalid */
+	if (state < ACPI_S1 || state > ACPI_S5) 
+		goto acpi_sleep_done;
+
+	/* make sure we don't get any suprises */
+	disable();
+
+	/* TODO: save device state and suspend them */
+	
+	/* save the processor state to memory if going into S2 or S3;
+	 * save it to disk if going into S4.
+	 * Also, set the FWV if going into an STR state
+	 */
+	if (state == ACPI_S2 || state == ACPI_S3) {
+#ifdef DONT_USE_UNTIL_LOWLEVEL_CODE_EXISTS
+		wakeup_address = acpi_save_state_mem((unsigned long)&&acpi_sleep_done);
+
+		if (!wakeup_address) goto acpi_sleep_done;
+
+		acpi_set_firmware_waking_vector(
+			(ACPI_PHYSICAL_ADDRESS)wakeup_address);
+#endif
+	} else if (state == ACPI_S4)
+#ifdef DONT_USE_UNTIL_LOWLEVEL_CODE_EXISTS
+		if (acpi_save_state_disk((unsigned long)&&acpi_sleep_done)) 
+			goto acpi_sleep_done;
+#endif
+
+	/* set status, since acpi_enter_sleep_state won't return unless something
+	 * goes wrong, or it's just S1.
+	 */
+	status = AE_OK;
+
+	mdelay(10);
+	status = acpi_enter_sleep_state(state);
+
+ acpi_sleep_done:
+
+	/* pause for a bit to allow devices to come back on */
+	mdelay(10);
+
+	/* make sure that the firmware waking vector is reset */
+	acpi_set_firmware_waking_vector((ACPI_PHYSICAL_ADDRESS)0);
+
+	acpi_leave_sleep_state(state);
+
+	/* TODO: resume devices and restore their state */
+
+	enable();
+	return status;
+}
+
 
 /****************************************************************************
  *
@@ -188,10 +732,8 @@
 void
 sm_osl_power_down (void)
 {
-	ACPI_STATUS		status = AE_OK;
-
 	/* Power down the system (S5 = soft off). */
-	status = acpi_enter_sleep_state(ACPI_STATE_S5);
+	sm_osl_suspend(ACPI_S5);
 }
 
 
@@ -201,7 +743,7 @@
  *
  ****************************************************************************/
 
-ACPI_STATUS
+acpi_status
 sm_osl_add_device(
 	SM_CONTEXT		*system)
 {
@@ -222,13 +764,42 @@
 
 	if (system->states[ACPI_STATE_S5]) {
 		sm_pm_power_off = pm_power_off;
-		pm_power_off = sm_osl_power_down; 
+		pm_power_off = sm_osl_power_down;
 	}
 
-	create_proc_read_entry(SM_PROC_INFO, S_IRUGO, 
+	create_proc_read_entry(SM_PROC_INFO, S_IRUGO,
 		sm_proc_root, sm_osl_proc_read_info, (void*)system);
 
-	/* 
+	bm_proc_sleep = create_proc_read_entry("sleep", S_IFREG | S_IRUGO | S_IWUSR,
+					    sm_proc_root, sm_osl_proc_read_sleep, (void*)system);
+	if (bm_proc_sleep)
+		bm_proc_sleep->write_proc = sm_osl_proc_write_sleep;
+
+	bm_proc_alarm = create_proc_read_entry("alarm", S_IFREG | S_IRUGO | S_IWUSR,
+					       sm_proc_root,sm_osl_proc_read_alarm, NULL);
+	if (bm_proc_alarm)
+		bm_proc_alarm->write_proc = sm_osl_proc_write_alarm;
+
+	bm_proc_gpe = create_proc_read_entry("gpe", S_IFREG | S_IRUGO | S_IWUSR,
+					     sm_proc_root,sm_osl_proc_read_gpe,NULL);
+	if (bm_proc_gpe)
+		bm_proc_gpe->write_proc = sm_osl_proc_write_gpe;
+	
+	/*
+	 * Get a wakeup address for use when we come back from sleep.
+	 * At least on IA-32, this needs to be in low memory.
+	 * When sleep is supported on other arch's, then we may want
+	 * to move this out to another place, but GFP_LOW should suffice
+	 * for now.
+	 */
+#if 0
+	if (system->states[ACPI_S3] || system->states[ACPI_S4]) {
+		acpi_wakeup_address = (unsigned long)virt_to_phys(get_free_page(GFP_LOWMEM));
+		printk(KERN_INFO "ACPI: Have wakeup address 0x%8.8x\n",acpi_wakeup_address);
+	}
+#endif
+
+	/*
 	 * This returns more than a page, so we need to use our own file ops,
 	 * not proc's generic ones
 	 */
@@ -247,7 +818,7 @@
  *
  ****************************************************************************/
 
-ACPI_STATUS
+acpi_status
 sm_osl_remove_device (
 	SM_CONTEXT		*system)
 {
@@ -268,12 +839,12 @@
  *
  ****************************************************************************/
 
-ACPI_STATUS
+acpi_status
 sm_osl_generate_event (
 	u32			event,
 	SM_CONTEXT		*system)
 {
-	ACPI_STATUS		status = AE_OK;
+	acpi_status		status = AE_OK;
 
 	if (!system) {
 		return(AE_BAD_PARAMETER);
@@ -302,10 +873,14 @@
  *
  ****************************************************************************/
 
-static int __init 
+static int __init
 sm_osl_init (void)
 {
-	ACPI_STATUS		status = AE_OK;
+	acpi_status		status = AE_OK;
+
+	/* abort if no busmgr */
+	if (!bm_proc_root)
+		return -ENODEV;
 
 	sm_proc_root = bm_proc_root;
 	if (!sm_proc_root) {
@@ -331,7 +906,7 @@
  *
  ****************************************************************************/
 
-static void __exit 
+static void __exit
 sm_osl_cleanup (void)
 {
 	sm_terminate();

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)