Merge branch 'syscore' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspen...
[pandora-kernel.git] / drivers / acpi / nvs.c
1 /*
2  * nvs.c - Routines for saving and restoring ACPI NVS memory region
3  *
4  * Copyright (C) 2008-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
5  *
6  * This file is released under the GPLv2.
7  */
8
9 #include <linux/io.h>
10 #include <linux/kernel.h>
11 #include <linux/list.h>
12 #include <linux/mm.h>
13 #include <linux/slab.h>
14 #include <linux/acpi.h>
15 #include <linux/acpi_io.h>
16 #include <acpi/acpiosxf.h>
17
18 /*
19  * Platforms, like ACPI, may want us to save some memory used by them during
20  * suspend and to restore the contents of this memory during the subsequent
21  * resume.  The code below implements a mechanism allowing us to do that.
22  */
23
24 struct nvs_page {
25         unsigned long phys_start;
26         unsigned int size;
27         void *kaddr;
28         void *data;
29         bool unmap;
30         struct list_head node;
31 };
32
33 static LIST_HEAD(nvs_list);
34
35 /**
36  *      suspend_nvs_register - register platform NVS memory region to save
37  *      @start - physical address of the region
38  *      @size - size of the region
39  *
40  *      The NVS region need not be page-aligned (both ends) and we arrange
41  *      things so that the data from page-aligned addresses in this region will
42  *      be copied into separate RAM pages.
43  */
44 int suspend_nvs_register(unsigned long start, unsigned long size)
45 {
46         struct nvs_page *entry, *next;
47
48         pr_info("PM: Registering ACPI NVS region at %lx (%ld bytes)\n",
49                 start, size);
50
51         while (size > 0) {
52                 unsigned int nr_bytes;
53
54                 entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
55                 if (!entry)
56                         goto Error;
57
58                 list_add_tail(&entry->node, &nvs_list);
59                 entry->phys_start = start;
60                 nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
61                 entry->size = (size < nr_bytes) ? size : nr_bytes;
62
63                 start += entry->size;
64                 size -= entry->size;
65         }
66         return 0;
67
68  Error:
69         list_for_each_entry_safe(entry, next, &nvs_list, node) {
70                 list_del(&entry->node);
71                 kfree(entry);
72         }
73         return -ENOMEM;
74 }
75
76 /**
77  *      suspend_nvs_free - free data pages allocated for saving NVS regions
78  */
79 void suspend_nvs_free(void)
80 {
81         struct nvs_page *entry;
82
83         list_for_each_entry(entry, &nvs_list, node)
84                 if (entry->data) {
85                         free_page((unsigned long)entry->data);
86                         entry->data = NULL;
87                         if (entry->kaddr) {
88                                 if (entry->unmap) {
89                                         iounmap(entry->kaddr);
90                                         entry->unmap = false;
91                                 } else {
92                                         acpi_os_unmap_memory(entry->kaddr,
93                                                              entry->size);
94                                 }
95                                 entry->kaddr = NULL;
96                         }
97                 }
98 }
99
100 /**
101  *      suspend_nvs_alloc - allocate memory necessary for saving NVS regions
102  */
103 int suspend_nvs_alloc(void)
104 {
105         struct nvs_page *entry;
106
107         list_for_each_entry(entry, &nvs_list, node) {
108                 entry->data = (void *)__get_free_page(GFP_KERNEL);
109                 if (!entry->data) {
110                         suspend_nvs_free();
111                         return -ENOMEM;
112                 }
113         }
114         return 0;
115 }
116
117 /**
118  *      suspend_nvs_save - save NVS memory regions
119  */
120 int suspend_nvs_save(void)
121 {
122         struct nvs_page *entry;
123
124         printk(KERN_INFO "PM: Saving platform NVS memory\n");
125
126         list_for_each_entry(entry, &nvs_list, node)
127                 if (entry->data) {
128                         unsigned long phys = entry->phys_start;
129                         unsigned int size = entry->size;
130
131                         entry->kaddr = acpi_os_get_iomem(phys, size);
132                         if (!entry->kaddr) {
133                                 entry->kaddr = acpi_os_ioremap(phys, size);
134                                 entry->unmap = !!entry->kaddr;
135                         }
136                         if (!entry->kaddr) {
137                                 suspend_nvs_free();
138                                 return -ENOMEM;
139                         }
140                         memcpy(entry->data, entry->kaddr, entry->size);
141                 }
142
143         return 0;
144 }
145
146 /**
147  *      suspend_nvs_restore - restore NVS memory regions
148  *
149  *      This function is going to be called with interrupts disabled, so it
150  *      cannot iounmap the virtual addresses used to access the NVS region.
151  */
152 void suspend_nvs_restore(void)
153 {
154         struct nvs_page *entry;
155
156         printk(KERN_INFO "PM: Restoring platform NVS memory\n");
157
158         list_for_each_entry(entry, &nvs_list, node)
159                 if (entry->data)
160                         memcpy(entry->kaddr, entry->data, entry->size);
161 }