163 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2 3/*4 * acpi_lpit.c - LPIT table processing functions5 *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#include "internal.h"14 15struct lpit_residency_info {16 struct acpi_generic_address gaddr;17 u64 frequency;18 void __iomem *iomem_addr;19};20 21/* Storage for an memory mapped and FFH based entries */22static struct lpit_residency_info residency_info_mem;23static struct lpit_residency_info residency_info_ffh;24 25static int lpit_read_residency_counter_us(u64 *counter, bool io_mem)26{27 int err;28 29 if (io_mem) {30 u64 count = 0;31 int error;32 33 error = acpi_os_read_iomem(residency_info_mem.iomem_addr, &count,34 residency_info_mem.gaddr.bit_width);35 if (error)36 return error;37 38 *counter = div64_u64(count * 1000000ULL, residency_info_mem.frequency);39 return 0;40 }41 42 err = rdmsrl_safe(residency_info_ffh.gaddr.address, counter);43 if (!err) {44 u64 mask = GENMASK_ULL(residency_info_ffh.gaddr.bit_offset +45 residency_info_ffh.gaddr. bit_width - 1,46 residency_info_ffh.gaddr.bit_offset);47 48 *counter &= mask;49 *counter >>= residency_info_ffh.gaddr.bit_offset;50 *counter = div64_u64(*counter * 1000000ULL, residency_info_ffh.frequency);51 return 0;52 }53 54 return -ENODATA;55}56 57static ssize_t low_power_idle_system_residency_us_show(struct device *dev,58 struct device_attribute *attr,59 char *buf)60{61 u64 counter;62 int ret;63 64 ret = lpit_read_residency_counter_us(&counter, true);65 if (ret)66 return ret;67 68 return sprintf(buf, "%llu\n", counter);69}70static DEVICE_ATTR_RO(low_power_idle_system_residency_us);71 72static ssize_t low_power_idle_cpu_residency_us_show(struct device *dev,73 struct device_attribute *attr,74 char *buf)75{76 u64 counter;77 int ret;78 79 ret = lpit_read_residency_counter_us(&counter, false);80 if (ret)81 return ret;82 83 return sprintf(buf, "%llu\n", counter);84}85static DEVICE_ATTR_RO(low_power_idle_cpu_residency_us);86 87int lpit_read_residency_count_address(u64 *address)88{89 if (!residency_info_mem.gaddr.address)90 return -EINVAL;91 92 *address = residency_info_mem.gaddr.address;93 94 return 0;95}96EXPORT_SYMBOL_GPL(lpit_read_residency_count_address);97 98static void lpit_update_residency(struct lpit_residency_info *info,99 struct acpi_lpit_native *lpit_native)100{101 struct device *dev_root = bus_get_dev_root(&cpu_subsys);102 103 /* Silently fail, if cpuidle attribute group is not present */104 if (!dev_root)105 return;106 107 info->frequency = lpit_native->counter_frequency ?108 lpit_native->counter_frequency : mul_u32_u32(tsc_khz, 1000U);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(info->gaddr.address,115 info->gaddr.bit_width / 8);116 if (!info->iomem_addr)117 goto exit;118 119 sysfs_add_file_to_group(&dev_root->kobj,120 &dev_attr_low_power_idle_system_residency_us.attr,121 "cpuidle");122 } else if (info->gaddr.space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) {123 sysfs_add_file_to_group(&dev_root->kobj,124 &dev_attr_low_power_idle_cpu_residency_us.attr,125 "cpuidle");126 }127exit:128 put_device(dev_root);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 struct acpi_table_lpit *lpit;153 154 status = acpi_get_table(ACPI_SIG_LPIT, 0, (struct acpi_table_header **)&lpit);155 if (ACPI_FAILURE(status))156 return;157 158 lpit_process((u64)lpit + sizeof(*lpit),159 (u64)lpit + lpit->header.length);160 161 acpi_put_table((struct acpi_table_header *)lpit);162}163