Srinivas Pandruvada | eeb2d80 | 2017-10-05 16:24:03 -0700 | [diff] [blame] | 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 | |
| 104 | static 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 | |
| 131 | static 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 | |
| 149 | void 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 | } |