main.c 5.4 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3
/*
 * sleep.c - ACPI sleep support.
 *
A
Alexey Starikovskiy 已提交
4
 * Copyright (c) 2005 Alexey Starikovskiy <alexey.y.starikovskiy@intel.com>
L
Linus Torvalds 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 * Copyright (c) 2004 David Shaohua Li <shaohua.li@intel.com>
 * Copyright (c) 2000-2003 Patrick Mochel
 * Copyright (c) 2003 Open Source Development Lab
 *
 * This file is released under the GPLv2.
 *
 */

#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/dmi.h>
#include <linux/device.h>
#include <linux/suspend.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
#include "sleep.h"

u8 sleep_states[ACPI_S_STATE_COUNT];

static struct pm_ops acpi_pm_ops;

extern void do_suspend_lowlevel(void);

static u32 acpi_suspend_states[] = {
A
Alexey Starikovskiy 已提交
29 30 31 32 33
	[PM_SUSPEND_ON] = ACPI_STATE_S0,
	[PM_SUSPEND_STANDBY] = ACPI_STATE_S1,
	[PM_SUSPEND_MEM] = ACPI_STATE_S3,
	[PM_SUSPEND_DISK] = ACPI_STATE_S4,
	[PM_SUSPEND_MAX] = ACPI_STATE_S5
L
Linus Torvalds 已提交
34 35 36 37 38 39 40 41 42 43 44 45 46
};

static int init_8259A_after_S1;

/**
 *	acpi_pm_prepare - Do preliminary suspend work.
 *	@pm_state:		suspend state we're entering.
 *
 *	Make sure we support the state. If we do, and we need it, set the
 *	firmware waking vector and do arch-specific nastiness to get the 
 *	wakeup code to the waking vector. 
 */

A
Alexey Starikovskiy 已提交
47 48 49
extern int acpi_sleep_prepare(u32 acpi_state);
extern void acpi_power_off(void);

L
Linus Torvalds 已提交
50 51 52 53
static int acpi_pm_prepare(suspend_state_t pm_state)
{
	u32 acpi_state = acpi_suspend_states[pm_state];

A
Alexey Starikovskiy 已提交
54 55
	if (!sleep_states[acpi_state]) {
		printk("acpi_pm_prepare does not support %d \n", pm_state);
L
Linus Torvalds 已提交
56 57
		return -EPERM;
	}
A
Alexey Starikovskiy 已提交
58
	return acpi_sleep_prepare(acpi_state);
L
Linus Torvalds 已提交
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
}

/**
 *	acpi_pm_enter - Actually enter a sleep state.
 *	@pm_state:		State we're entering.
 *
 *	Flush caches and go to sleep. For STR or STD, we have to call 
 *	arch-specific assembly, which in turn call acpi_enter_sleep_state().
 *	It's unfortunate, but it works. Please fix if you're feeling frisky.
 */

static int acpi_pm_enter(suspend_state_t pm_state)
{
	acpi_status status = AE_OK;
	unsigned long flags = 0;
	u32 acpi_state = acpi_suspend_states[pm_state];

	ACPI_FLUSH_CPU_CACHE();

	/* Do arch specific saving of state. */
	if (pm_state > PM_SUSPEND_STANDBY) {
		int error = acpi_save_state_mem();
		if (error)
			return error;
	}

	local_irq_save(flags);
	acpi_enable_wakeup_device(acpi_state);
A
Alexey Starikovskiy 已提交
87
	switch (pm_state) {
L
Linus Torvalds 已提交
88 89 90 91 92 93 94 95 96 97 98 99 100
	case PM_SUSPEND_STANDBY:
		barrier();
		status = acpi_enter_sleep_state(acpi_state);
		break;

	case PM_SUSPEND_MEM:
		do_suspend_lowlevel();
		break;

	case PM_SUSPEND_DISK:
		if (acpi_pm_ops.pm_disk_mode == PM_DISK_PLATFORM)
			status = acpi_enter_sleep_state(acpi_state);
		break;
A
Alexey Starikovskiy 已提交
101 102 103 104
	case PM_SUSPEND_MAX:
		acpi_power_off();
		break;

L
Linus Torvalds 已提交
105 106 107
	default:
		return -EINVAL;
	}
108 109 110 111 112 113 114 115

	/* ACPI 3.0 specs (P62) says that it's the responsabilty
	 * of the OSPM to clear the status bit [ implying that the
	 * POWER_BUTTON event should not reach userspace ]
	 */
	if (ACPI_SUCCESS(status) && (acpi_state == ACPI_STATE_S3))
		acpi_clear_event(ACPI_EVENT_POWER_BUTTON);

L
Linus Torvalds 已提交
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
	local_irq_restore(flags);
	printk(KERN_DEBUG "Back to C!\n");

	/* restore processor state
	 * We should only be here if we're coming back from STR or STD.
	 * And, in the case of the latter, the memory image should have already
	 * been loaded from disk.
	 */
	if (pm_state > PM_SUSPEND_STANDBY)
		acpi_restore_state_mem();

	return ACPI_SUCCESS(status) ? 0 : -EFAULT;
}

/**
 *	acpi_pm_finish - Finish up suspend sequence.
 *	@pm_state:		State we're coming out of.
 *
 *	This is called after we wake back up (or if entering the sleep state
 *	failed). 
 */

static int acpi_pm_finish(suspend_state_t pm_state)
{
	u32 acpi_state = acpi_suspend_states[pm_state];

	acpi_leave_sleep_state(acpi_state);
	acpi_disable_wakeup_device(acpi_state);

	/* reset firmware waking vector */
	acpi_set_firmware_waking_vector((acpi_physical_address) 0);

	if (init_8259A_after_S1) {
		printk("Broken toshiba laptop -> kicking interrupts\n");
		init_8259A(0);
	}
	return 0;
}

int acpi_suspend(u32 acpi_state)
{
	suspend_state_t states[] = {
A
Alexey Starikovskiy 已提交
158 159 160 161
		[1] = PM_SUSPEND_STANDBY,
		[3] = PM_SUSPEND_MEM,
		[4] = PM_SUSPEND_DISK,
		[5] = PM_SUSPEND_MAX
L
Linus Torvalds 已提交
162 163
	};

A
Alexey Starikovskiy 已提交
164
	if (acpi_state < 6 && states[acpi_state])
L
Linus Torvalds 已提交
165 166 167 168
		return pm_suspend(states[acpi_state]);
	return -EINVAL;
}

169 170 171 172 173 174 175
static int acpi_pm_state_valid(suspend_state_t pm_state)
{
	u32 acpi_state = acpi_suspend_states[pm_state];

	return sleep_states[acpi_state];
}

L
Linus Torvalds 已提交
176
static struct pm_ops acpi_pm_ops = {
177
	.valid = acpi_pm_state_valid,
A
Alexey Starikovskiy 已提交
178 179 180
	.prepare = acpi_pm_prepare,
	.enter = acpi_pm_enter,
	.finish = acpi_pm_finish,
L
Linus Torvalds 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
};

/*
 * Toshiba fails to preserve interrupts over S1, reinitialization
 * of 8259 is needed after S1 resume.
 */
static int __init init_ints_after_s1(struct dmi_system_id *d)
{
	printk(KERN_WARNING "%s with broken S1 detected.\n", d->ident);
	init_8259A_after_S1 = 1;
	return 0;
}

static struct dmi_system_id __initdata acpisleep_dmi_table[] = {
	{
A
Alexey Starikovskiy 已提交
196 197 198 199 200
	 .callback = init_ints_after_s1,
	 .ident = "Toshiba Satellite 4030cdt",
	 .matches = {DMI_MATCH(DMI_PRODUCT_NAME, "S4030CDT/4.3"),},
	 },
	{},
L
Linus Torvalds 已提交
201 202
};

203
int __init acpi_sleep_init(void)
L
Linus Torvalds 已提交
204
{
A
Alexey Starikovskiy 已提交
205
	int i = 0;
L
Linus Torvalds 已提交
206 207 208 209 210 211 212

	dmi_check_system(acpisleep_dmi_table);

	if (acpi_disabled)
		return 0;

	printk(KERN_INFO PREFIX "(supports");
A
Alexey Starikovskiy 已提交
213
	for (i = 0; i < ACPI_S_STATE_COUNT; i++) {
L
Linus Torvalds 已提交
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
		acpi_status status;
		u8 type_a, type_b;
		status = acpi_get_sleep_type_data(i, &type_a, &type_b);
		if (ACPI_SUCCESS(status)) {
			sleep_states[i] = 1;
			printk(" S%d", i);
		}
		if (i == ACPI_STATE_S4) {
			if (sleep_states[i])
				acpi_pm_ops.pm_disk_mode = PM_DISK_PLATFORM;
		}
	}
	printk(")\n");

	pm_set_ops(&acpi_pm_ops);
	return 0;
}