summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/acpi/lpit.txt25
-rw-r--r--drivers/acpi/Kconfig5
-rw-r--r--drivers/acpi/Makefile1
-rw-r--r--drivers/acpi/acpi_lpit.c162
-rw-r--r--drivers/acpi/internal.h6
-rw-r--r--drivers/acpi/osl.c42
-rw-r--r--drivers/acpi/scan.c1
-rw-r--r--include/acpi/acpiosxf.h2
-rw-r--r--include/linux/acpi.h9
9 files changed, 237 insertions, 16 deletions
diff --git a/Documentation/acpi/lpit.txt b/Documentation/acpi/lpit.txt
new file mode 100644
index 000000000000..b426398d2e97
--- /dev/null
+++ b/Documentation/acpi/lpit.txt
@@ -0,0 +1,25 @@
1To enumerate platform Low Power Idle states, Intel platforms are using
2“Low Power Idle Table” (LPIT). More details about this table can be
3downloaded from:
4http://www.uefi.org/sites/default/files/resources/Intel_ACPI_Low_Power_S0_Idle.pdf
5
6Residencies for each low power state can be read via FFH
7(Function fixed hardware) or a memory mapped interface.
8
9On platforms supporting S0ix sleep states, there can be two types of
10residencies:
11- CPU PKG C10 (Read via FFH interface)
12- Platform Controller Hub (PCH) SLP_S0 (Read via memory mapped interface)
13
14The following attributes are added dynamically to the cpuidle
15sysfs attribute group:
16 /sys/devices/system/cpu/cpuidle/low_power_idle_cpu_residency_us
17 /sys/devices/system/cpu/cpuidle/low_power_idle_system_residency_us
18
19The "low_power_idle_cpu_residency_us" attribute shows time spent
20by the CPU package in PKG C10
21
22The "low_power_idle_system_residency_us" attribute shows SLP_S0
23residency, or system time spent with the SLP_S0# signal asserted.
24This is the lowest possible system power state, achieved only when CPU is in
25PKG C10 and all functional blocks in PCH are in a low power state.
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 1ce52f84dc23..4bfef0f78cde 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -80,6 +80,11 @@ endif
80config ACPI_SPCR_TABLE 80config ACPI_SPCR_TABLE
81 bool 81 bool
82 82
83config ACPI_LPIT
84 bool
85 depends on X86_64
86 default y
87
83config ACPI_SLEEP 88config ACPI_SLEEP
84 bool 89 bool
85 depends on SUSPEND || HIBERNATION 90 depends on SUSPEND || HIBERNATION
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 90265ab4437a..6a19bd7aba21 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -56,6 +56,7 @@ acpi-$(CONFIG_DEBUG_FS) += debugfs.o
56acpi-$(CONFIG_ACPI_NUMA) += numa.o 56acpi-$(CONFIG_ACPI_NUMA) += numa.o
57acpi-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o 57acpi-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o
58acpi-y += acpi_lpat.o 58acpi-y += acpi_lpat.o
59acpi-$(CONFIG_ACPI_LPIT) += acpi_lpit.o
59acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o 60acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o
60acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o 61acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o
61 62
diff --git a/drivers/acpi/acpi_lpit.c b/drivers/acpi/acpi_lpit.c
new file mode 100644
index 000000000000..e94e478dd18b
--- /dev/null
+++ b/drivers/acpi/acpi_lpit.c
@@ -0,0 +1,162 @@
1
2/*
3 * acpi_lpit.c - LPIT table processing functions
4 *
5 * Copyright (C) 2017 Intel Corporation. All rights reserved.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License version
9 * 2 as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17#include <linux/cpu.h>
18#include <linux/acpi.h>
19#include <asm/msr.h>
20#include <asm/tsc.h>
21
22struct lpit_residency_info {
23 struct acpi_generic_address gaddr;
24 u64 frequency;
25 void __iomem *iomem_addr;
26};
27
28/* Storage for an memory mapped and FFH based entries */
29static struct lpit_residency_info residency_info_mem;
30static struct lpit_residency_info residency_info_ffh;
31
32static int lpit_read_residency_counter_us(u64 *counter, bool io_mem)
33{
34 int err;
35
36 if (io_mem) {
37 u64 count = 0;
38 int error;
39
40 error = acpi_os_read_iomem(residency_info_mem.iomem_addr, &count,
41 residency_info_mem.gaddr.bit_width);
42 if (error)
43 return error;
44
45 *counter = div64_u64(count * 1000000ULL, residency_info_mem.frequency);
46 return 0;
47 }
48
49 err = rdmsrl_safe(residency_info_ffh.gaddr.address, counter);
50 if (!err) {
51 u64 mask = GENMASK_ULL(residency_info_ffh.gaddr.bit_offset +
52 residency_info_ffh.gaddr. bit_width - 1,
53 residency_info_ffh.gaddr.bit_offset);
54
55 *counter &= mask;
56 *counter >>= residency_info_ffh.gaddr.bit_offset;
57 *counter = div64_u64(*counter * 1000000ULL, residency_info_ffh.frequency);
58 return 0;
59 }
60
61 return -ENODATA;
62}
63
64static ssize_t low_power_idle_system_residency_us_show(struct device *dev,
65 struct device_attribute *attr,
66 char *buf)
67{
68 u64 counter;
69 int ret;
70
71 ret = lpit_read_residency_counter_us(&counter, true);
72 if (ret)
73 return ret;
74
75 return sprintf(buf, "%llu\n", counter);
76}
77static DEVICE_ATTR_RO(low_power_idle_system_residency_us);
78
79static ssize_t low_power_idle_cpu_residency_us_show(struct device *dev,
80 struct device_attribute *attr,
81 char *buf)
82{
83 u64 counter;
84 int ret;
85
86 ret = lpit_read_residency_counter_us(&counter, false);
87 if (ret)
88 return ret;
89
90 return sprintf(buf, "%llu\n", counter);
91}
92static DEVICE_ATTR_RO(low_power_idle_cpu_residency_us);
93
94int lpit_read_residency_count_address(u64 *address)
95{
96 if (!residency_info_mem.gaddr.address)
97 return -EINVAL;
98
99 *address = residency_info_mem.gaddr.address;
100
101 return 0;
102}
103
104static void lpit_update_residency(struct lpit_residency_info *info,
105 struct acpi_lpit_native *lpit_native)
106{
107 info->frequency = lpit_native->counter_frequency ?
108 lpit_native->counter_frequency : tsc_khz * 1000;
109 if (!info->frequency)
110 info->frequency = 1;
111
112 info->gaddr = lpit_native->residency_counter;
113 if (info->gaddr.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
114 info->iomem_addr = ioremap_nocache(info->gaddr.address,
115 info->gaddr.bit_width / 8);
116 if (!info->iomem_addr)
117 return;
118
119 /* Silently fail, if cpuidle attribute group is not present */
120 sysfs_add_file_to_group(&cpu_subsys.dev_root->kobj,
121 &dev_attr_low_power_idle_system_residency_us.attr,
122 "cpuidle");
123 } else if (info->gaddr.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) {
124 /* Silently fail, if cpuidle attribute group is not present */
125 sysfs_add_file_to_group(&cpu_subsys.dev_root->kobj,
126 &dev_attr_low_power_idle_cpu_residency_us.attr,
127 "cpuidle");
128 }
129}
130
131static void lpit_process(u64 begin, u64 end)
132{
133 while (begin + sizeof(struct acpi_lpit_native) < end) {
134 struct acpi_lpit_native *lpit_native = (struct acpi_lpit_native *)begin;
135
136 if (!lpit_native->header.type && !lpit_native->header.flags) {
137 if (lpit_native->residency_counter.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY &&
138 !residency_info_mem.gaddr.address) {
139 lpit_update_residency(&residency_info_mem, lpit_native);
140 } else if (lpit_native->residency_counter.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE &&
141 !residency_info_ffh.gaddr.address) {
142 lpit_update_residency(&residency_info_ffh, lpit_native);
143 }
144 }
145 begin += lpit_native->header.length;
146 }
147}
148
149void acpi_init_lpit(void)
150{
151 acpi_status status;
152 u64 lpit_begin;
153 struct acpi_table_lpit *lpit;
154
155 status = acpi_get_table(ACPI_SIG_LPIT, 0, (struct acpi_table_header **)&lpit);
156
157 if (ACPI_FAILURE(status))
158 return;
159
160 lpit_begin = (u64)lpit + sizeof(*lpit);
161 lpit_process(lpit_begin, lpit_begin + lpit->header.length);
162}
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index 4361c4415b4f..fc8c43e76707 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -248,4 +248,10 @@ void acpi_watchdog_init(void);
248static inline void acpi_watchdog_init(void) {} 248static inline void acpi_watchdog_init(void) {}
249#endif 249#endif
250 250
251#ifdef CONFIG_ACPI_LPIT
252void acpi_init_lpit(void);
253#else
254static inline void acpi_init_lpit(void) { }
255#endif
256
251#endif /* _ACPI_INTERNAL_H_ */ 257#endif /* _ACPI_INTERNAL_H_ */
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c
index db78d353bab1..3bb46cb24a99 100644
--- a/drivers/acpi/osl.c
+++ b/drivers/acpi/osl.c
@@ -663,6 +663,29 @@ acpi_status acpi_os_write_port(acpi_io_address port, u32 value, u32 width)
663 663
664EXPORT_SYMBOL(acpi_os_write_port); 664EXPORT_SYMBOL(acpi_os_write_port);
665 665
666int acpi_os_read_iomem(void __iomem *virt_addr, u64 *value, u32 width)
667{
668
669 switch (width) {
670 case 8:
671 *(u8 *) value = readb(virt_addr);
672 break;
673 case 16:
674 *(u16 *) value = readw(virt_addr);
675 break;
676 case 32:
677 *(u32 *) value = readl(virt_addr);
678 break;
679 case 64:
680 *(u64 *) value = readq(virt_addr);
681 break;
682 default:
683 return -EINVAL;
684 }
685
686 return 0;
687}
688
666acpi_status 689acpi_status
667acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width) 690acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width)
668{ 691{
@@ -670,6 +693,7 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width)
670 unsigned int size = width / 8; 693 unsigned int size = width / 8;
671 bool unmap = false; 694 bool unmap = false;
672 u64 dummy; 695 u64 dummy;
696 int error;
673 697
674 rcu_read_lock(); 698 rcu_read_lock();
675 virt_addr = acpi_map_vaddr_lookup(phys_addr, size); 699 virt_addr = acpi_map_vaddr_lookup(phys_addr, size);
@@ -684,22 +708,8 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width)
684 if (!value) 708 if (!value)
685 value = &dummy; 709 value = &dummy;
686 710
687 switch (width) { 711 error = acpi_os_read_iomem(virt_addr, value, width);
688 case 8: 712 BUG_ON(error);
689 *(u8 *) value = readb(virt_addr);
690 break;
691 case 16:
692 *(u16 *) value = readw(virt_addr);
693 break;
694 case 32:
695 *(u32 *) value = readl(virt_addr);
696 break;
697 case 64:
698 *(u64 *) value = readq(virt_addr);
699 break;
700 default:
701 BUG();
702 }
703 713
704 if (unmap) 714 if (unmap)
705 iounmap(virt_addr); 715 iounmap(virt_addr);
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 602f8ff212f2..81367edc8a10 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -2122,6 +2122,7 @@ int __init acpi_scan_init(void)
2122 acpi_int340x_thermal_init(); 2122 acpi_int340x_thermal_init();
2123 acpi_amba_init(); 2123 acpi_amba_init();
2124 acpi_watchdog_init(); 2124 acpi_watchdog_init();
2125 acpi_init_lpit();
2125 2126
2126 acpi_scan_add_handler(&generic_device_handler); 2127 acpi_scan_add_handler(&generic_device_handler);
2127 2128
diff --git a/include/acpi/acpiosxf.h b/include/acpi/acpiosxf.h
index c66eb8ffa454..d5c0f5153c4e 100644
--- a/include/acpi/acpiosxf.h
+++ b/include/acpi/acpiosxf.h
@@ -287,6 +287,8 @@ acpi_status acpi_os_write_port(acpi_io_address address, u32 value, u32 width);
287/* 287/*
288 * Platform and hardware-independent physical memory interfaces 288 * Platform and hardware-independent physical memory interfaces
289 */ 289 */
290int acpi_os_read_iomem(void __iomem *virt_addr, u64 *value, u32 width);
291
290#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_read_memory 292#ifndef ACPI_USE_ALTERNATE_PROTOTYPE_acpi_os_read_memory
291acpi_status 293acpi_status
292acpi_os_read_memory(acpi_physical_address address, u64 *value, u32 width); 294acpi_os_read_memory(acpi_physical_address address, u64 *value, u32 width);
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index d18c92d4ba19..2b1738f840ab 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -1248,4 +1248,13 @@ int acpi_irq_get(acpi_handle handle, unsigned int index, struct resource *res)
1248} 1248}
1249#endif 1249#endif
1250 1250
1251#ifdef CONFIG_ACPI_LPIT
1252int lpit_read_residency_count_address(u64 *address);
1253#else
1254static inline int lpit_read_residency_count_address(u64 *address)
1255{
1256 return -EINVAL;
1257}
1258#endif
1259
1251#endif /*_LINUX_ACPI_H*/ 1260#endif /*_LINUX_ACPI_H*/