1 // SPDX-License-Identifier: GPL-2.0-only 2 3 /* 4 * acpi_lpit.c - LPIT table processing functions 5 * 6 * Copyright (C) 2017 Intel Corporation. All rights reserved. 7 */ 8 9 #include <linux/cpu.h> 10 #include <linux/acpi.h> 11 #include <asm/msr.h> 12 #include <asm/tsc.h> 13 14 struct lpit_residency_info { 15 struct acpi_generic_address gaddr; 16 u64 frequency; 17 void __iomem *iomem_addr; 18 }; 19 20 /* Storage for an memory mapped and FFH based entries */ 21 static struct lpit_residency_info residency_info_mem; 22 static struct lpit_residency_info residency_info_ffh; 23 24 static int lpit_read_residency_counter_us(u64 *counter, bool io_mem) 25 { 26 int err; 27 28 if (io_mem) { 29 u64 count = 0; 30 int error; 31 32 error = acpi_os_read_iomem(residency_info_mem.iomem_addr, &count, 33 residency_info_mem.gaddr.bit_width); 34 if (error) 35 return error; 36 37 *counter = div64_u64(count * 1000000ULL, residency_info_mem.frequency); 38 return 0; 39 } 40 41 err = rdmsrl_safe(residency_info_ffh.gaddr.address, counter); 42 if (!err) { 43 u64 mask = GENMASK_ULL(residency_info_ffh.gaddr.bit_offset + 44 residency_info_ffh.gaddr. bit_width - 1, 45 residency_info_ffh.gaddr.bit_offset); 46 47 *counter &= mask; 48 *counter >>= residency_info_ffh.gaddr.bit_offset; 49 *counter = div64_u64(*counter * 1000000ULL, residency_info_ffh.frequency); 50 return 0; 51 } 52 53 return -ENODATA; 54 } 55 56 static ssize_t low_power_idle_system_residency_us_show(struct device *dev, 57 struct device_attribute *attr, 58 char *buf) 59 { 60 u64 counter; 61 int ret; 62 63 ret = lpit_read_residency_counter_us(&counter, true); 64 if (ret) 65 return ret; 66 67 return sprintf(buf, "%llu\n", counter); 68 } 69 static DEVICE_ATTR_RO(low_power_idle_system_residency_us); 70 71 static ssize_t low_power_idle_cpu_residency_us_show(struct device *dev, 72 struct device_attribute *attr, 73 char *buf) 74 { 75 u64 counter; 76 int ret; 77 78 ret = lpit_read_residency_counter_us(&counter, false); 79 if (ret) 80 return ret; 81 82 return sprintf(buf, "%llu\n", counter); 83 } 84 static DEVICE_ATTR_RO(low_power_idle_cpu_residency_us); 85 86 int lpit_read_residency_count_address(u64 *address) 87 { 88 if (!residency_info_mem.gaddr.address) 89 return -EINVAL; 90 91 *address = residency_info_mem.gaddr.address; 92 93 return 0; 94 } 95 EXPORT_SYMBOL_GPL(lpit_read_residency_count_address); 96 97 static void lpit_update_residency(struct lpit_residency_info *info, 98 struct acpi_lpit_native *lpit_native) 99 { 100 info->frequency = lpit_native->counter_frequency ? 101 lpit_native->counter_frequency : tsc_khz * 1000; 102 if (!info->frequency) 103 info->frequency = 1; 104 105 info->gaddr = lpit_native->residency_counter; 106 if (info->gaddr.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { 107 info->iomem_addr = ioremap(info->gaddr.address, 108 info->gaddr.bit_width / 8); 109 if (!info->iomem_addr) 110 return; 111 112 if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)) 113 return; 114 115 /* Silently fail, if cpuidle attribute group is not present */ 116 sysfs_add_file_to_group(&cpu_subsys.dev_root->kobj, 117 &dev_attr_low_power_idle_system_residency_us.attr, 118 "cpuidle"); 119 } else if (info->gaddr.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) { 120 if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)) 121 return; 122 123 /* Silently fail, if cpuidle attribute group is not present */ 124 sysfs_add_file_to_group(&cpu_subsys.dev_root->kobj, 125 &dev_attr_low_power_idle_cpu_residency_us.attr, 126 "cpuidle"); 127 } 128 } 129 130 static void lpit_process(u64 begin, u64 end) 131 { 132 while (begin + sizeof(struct acpi_lpit_native) <= end) { 133 struct acpi_lpit_native *lpit_native = (struct acpi_lpit_native *)begin; 134 135 if (!lpit_native->header.type && !lpit_native->header.flags) { 136 if (lpit_native->residency_counter.space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY && 137 !residency_info_mem.gaddr.address) { 138 lpit_update_residency(&residency_info_mem, lpit_native); 139 } else if (lpit_native->residency_counter.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE && 140 !residency_info_ffh.gaddr.address) { 141 lpit_update_residency(&residency_info_ffh, lpit_native); 142 } 143 } 144 begin += lpit_native->header.length; 145 } 146 } 147 148 void acpi_init_lpit(void) 149 { 150 acpi_status status; 151 struct acpi_table_lpit *lpit; 152 153 status = acpi_get_table(ACPI_SIG_LPIT, 0, (struct acpi_table_header **)&lpit); 154 155 if (ACPI_FAILURE(status)) 156 return; 157 158 lpit_process((u64)lpit + sizeof(*lpit), 159 (u64)lpit + lpit->header.length); 160 } 161