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 22 struct 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 */ 29 static struct lpit_residency_info residency_info_mem; 30 static struct lpit_residency_info residency_info_ffh; 31 32 static 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 64 static 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 } 77 static DEVICE_ATTR_RO(low_power_idle_system_residency_us); 78 79 static 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 } 92 static DEVICE_ATTR_RO(low_power_idle_cpu_residency_us); 93 94 int 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 EXPORT_SYMBOL_GPL(lpit_read_residency_count_address); 104 105 static void lpit_update_residency(struct lpit_residency_info *info, 106 struct acpi_lpit_native *lpit_native) 107 { 108 info->frequency = lpit_native->counter_frequency ? 109 lpit_native->counter_frequency : tsc_khz * 1000; 110 if (!info->frequency) 111 info->frequency = 1; 112 113 info->gaddr = lpit_native->residency_counter; 114 if (info->gaddr.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { 115 info->iomem_addr = ioremap_nocache(info->gaddr.address, 116 info->gaddr.bit_width / 8); 117 if (!info->iomem_addr) 118 return; 119 120 /* Silently fail, if cpuidle attribute group is not present */ 121 sysfs_add_file_to_group(&cpu_subsys.dev_root->kobj, 122 &dev_attr_low_power_idle_system_residency_us.attr, 123 "cpuidle"); 124 } else if (info->gaddr.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) { 125 /* Silently fail, if cpuidle attribute group is not present */ 126 sysfs_add_file_to_group(&cpu_subsys.dev_root->kobj, 127 &dev_attr_low_power_idle_cpu_residency_us.attr, 128 "cpuidle"); 129 } 130 } 131 132 static void lpit_process(u64 begin, u64 end) 133 { 134 while (begin + sizeof(struct acpi_lpit_native) < end) { 135 struct acpi_lpit_native *lpit_native = (struct acpi_lpit_native *)begin; 136 137 if (!lpit_native->header.type && !lpit_native->header.flags) { 138 if (lpit_native->residency_counter.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY && 139 !residency_info_mem.gaddr.address) { 140 lpit_update_residency(&residency_info_mem, lpit_native); 141 } else if (lpit_native->residency_counter.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE && 142 !residency_info_ffh.gaddr.address) { 143 lpit_update_residency(&residency_info_ffh, lpit_native); 144 } 145 } 146 begin += lpit_native->header.length; 147 } 148 } 149 150 void acpi_init_lpit(void) 151 { 152 acpi_status status; 153 u64 lpit_begin; 154 struct acpi_table_lpit *lpit; 155 156 status = acpi_get_table(ACPI_SIG_LPIT, 0, (struct acpi_table_header **)&lpit); 157 158 if (ACPI_FAILURE(status)) 159 return; 160 161 lpit_begin = (u64)lpit + sizeof(*lpit); 162 lpit_process(lpit_begin, lpit_begin + lpit->header.length); 163 } 164