18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * pcc-cpufreq.c - Processor Clocking Control firmware cpufreq interface 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2009 Red Hat, Matthew Garrett <mjg@redhat.com> 58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. 68c2ecf20Sopenharmony_ci * Nagananda Chumbalkar <nagananda.chumbalkar@hp.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 118c2ecf20Sopenharmony_ci * it under the terms of the GNU General Public License as published by 128c2ecf20Sopenharmony_ci * the Free Software Foundation; version 2 of the License. 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * This program is distributed in the hope that it will be useful, but 158c2ecf20Sopenharmony_ci * WITHOUT ANY WARRANTY; without even the implied warranty of 168c2ecf20Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or NON 178c2ecf20Sopenharmony_ci * INFRINGEMENT. See the GNU General Public License for more details. 188c2ecf20Sopenharmony_ci * 198c2ecf20Sopenharmony_ci * You should have received a copy of the GNU General Public License along 208c2ecf20Sopenharmony_ci * with this program; if not, write to the Free Software Foundation, Inc., 218c2ecf20Sopenharmony_ci * 675 Mass Ave, Cambridge, MA 02139, USA. 228c2ecf20Sopenharmony_ci * 238c2ecf20Sopenharmony_ci * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 248c2ecf20Sopenharmony_ci */ 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include <linux/kernel.h> 278c2ecf20Sopenharmony_ci#include <linux/module.h> 288c2ecf20Sopenharmony_ci#include <linux/init.h> 298c2ecf20Sopenharmony_ci#include <linux/smp.h> 308c2ecf20Sopenharmony_ci#include <linux/sched.h> 318c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 328c2ecf20Sopenharmony_ci#include <linux/compiler.h> 338c2ecf20Sopenharmony_ci#include <linux/slab.h> 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#include <linux/acpi.h> 368c2ecf20Sopenharmony_ci#include <linux/io.h> 378c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 388c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#include <acpi/processor.h> 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define PCC_VERSION "1.10.00" 438c2ecf20Sopenharmony_ci#define POLL_LOOPS 300 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#define CMD_COMPLETE 0x1 468c2ecf20Sopenharmony_ci#define CMD_GET_FREQ 0x0 478c2ecf20Sopenharmony_ci#define CMD_SET_FREQ 0x1 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#define BUF_SZ 4 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistruct pcc_register_resource { 528c2ecf20Sopenharmony_ci u8 descriptor; 538c2ecf20Sopenharmony_ci u16 length; 548c2ecf20Sopenharmony_ci u8 space_id; 558c2ecf20Sopenharmony_ci u8 bit_width; 568c2ecf20Sopenharmony_ci u8 bit_offset; 578c2ecf20Sopenharmony_ci u8 access_size; 588c2ecf20Sopenharmony_ci u64 address; 598c2ecf20Sopenharmony_ci} __attribute__ ((packed)); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistruct pcc_memory_resource { 628c2ecf20Sopenharmony_ci u8 descriptor; 638c2ecf20Sopenharmony_ci u16 length; 648c2ecf20Sopenharmony_ci u8 space_id; 658c2ecf20Sopenharmony_ci u8 resource_usage; 668c2ecf20Sopenharmony_ci u8 type_specific; 678c2ecf20Sopenharmony_ci u64 granularity; 688c2ecf20Sopenharmony_ci u64 minimum; 698c2ecf20Sopenharmony_ci u64 maximum; 708c2ecf20Sopenharmony_ci u64 translation_offset; 718c2ecf20Sopenharmony_ci u64 address_length; 728c2ecf20Sopenharmony_ci} __attribute__ ((packed)); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic struct cpufreq_driver pcc_cpufreq_driver; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistruct pcc_header { 778c2ecf20Sopenharmony_ci u32 signature; 788c2ecf20Sopenharmony_ci u16 length; 798c2ecf20Sopenharmony_ci u8 major; 808c2ecf20Sopenharmony_ci u8 minor; 818c2ecf20Sopenharmony_ci u32 features; 828c2ecf20Sopenharmony_ci u16 command; 838c2ecf20Sopenharmony_ci u16 status; 848c2ecf20Sopenharmony_ci u32 latency; 858c2ecf20Sopenharmony_ci u32 minimum_time; 868c2ecf20Sopenharmony_ci u32 maximum_time; 878c2ecf20Sopenharmony_ci u32 nominal; 888c2ecf20Sopenharmony_ci u32 throttled_frequency; 898c2ecf20Sopenharmony_ci u32 minimum_frequency; 908c2ecf20Sopenharmony_ci}; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic void __iomem *pcch_virt_addr; 938c2ecf20Sopenharmony_cistatic struct pcc_header __iomem *pcch_hdr; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(pcc_lock); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic struct acpi_generic_address doorbell; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic u64 doorbell_preserve; 1008c2ecf20Sopenharmony_cistatic u64 doorbell_write; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic u8 OSC_UUID[16] = {0x9F, 0x2C, 0x9B, 0x63, 0x91, 0x70, 0x1f, 0x49, 1038c2ecf20Sopenharmony_ci 0xBB, 0x4F, 0xA5, 0x98, 0x2F, 0xA1, 0xB5, 0x46}; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistruct pcc_cpu { 1068c2ecf20Sopenharmony_ci u32 input_offset; 1078c2ecf20Sopenharmony_ci u32 output_offset; 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic struct pcc_cpu __percpu *pcc_cpu_info; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic int pcc_cpufreq_verify(struct cpufreq_policy_data *policy) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci cpufreq_verify_within_cpu_limits(policy); 1158c2ecf20Sopenharmony_ci return 0; 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic inline void pcc_cmd(void) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci u64 doorbell_value; 1218c2ecf20Sopenharmony_ci int i; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci acpi_read(&doorbell_value, &doorbell); 1248c2ecf20Sopenharmony_ci acpi_write((doorbell_value & doorbell_preserve) | doorbell_write, 1258c2ecf20Sopenharmony_ci &doorbell); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci for (i = 0; i < POLL_LOOPS; i++) { 1288c2ecf20Sopenharmony_ci if (ioread16(&pcch_hdr->status) & CMD_COMPLETE) 1298c2ecf20Sopenharmony_ci break; 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic inline void pcc_clear_mapping(void) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci if (pcch_virt_addr) 1368c2ecf20Sopenharmony_ci iounmap(pcch_virt_addr); 1378c2ecf20Sopenharmony_ci pcch_virt_addr = NULL; 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic unsigned int pcc_get_freq(unsigned int cpu) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci struct pcc_cpu *pcc_cpu_data; 1438c2ecf20Sopenharmony_ci unsigned int curr_freq; 1448c2ecf20Sopenharmony_ci unsigned int freq_limit; 1458c2ecf20Sopenharmony_ci u16 status; 1468c2ecf20Sopenharmony_ci u32 input_buffer; 1478c2ecf20Sopenharmony_ci u32 output_buffer; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci spin_lock(&pcc_lock); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci pr_debug("get: get_freq for CPU %d\n", cpu); 1528c2ecf20Sopenharmony_ci pcc_cpu_data = per_cpu_ptr(pcc_cpu_info, cpu); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci input_buffer = 0x1; 1558c2ecf20Sopenharmony_ci iowrite32(input_buffer, 1568c2ecf20Sopenharmony_ci (pcch_virt_addr + pcc_cpu_data->input_offset)); 1578c2ecf20Sopenharmony_ci iowrite16(CMD_GET_FREQ, &pcch_hdr->command); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci pcc_cmd(); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci output_buffer = 1628c2ecf20Sopenharmony_ci ioread32(pcch_virt_addr + pcc_cpu_data->output_offset); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* Clear the input buffer - we are done with the current command */ 1658c2ecf20Sopenharmony_ci memset_io((pcch_virt_addr + pcc_cpu_data->input_offset), 0, BUF_SZ); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci status = ioread16(&pcch_hdr->status); 1688c2ecf20Sopenharmony_ci if (status != CMD_COMPLETE) { 1698c2ecf20Sopenharmony_ci pr_debug("get: FAILED: for CPU %d, status is %d\n", 1708c2ecf20Sopenharmony_ci cpu, status); 1718c2ecf20Sopenharmony_ci goto cmd_incomplete; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci iowrite16(0, &pcch_hdr->status); 1748c2ecf20Sopenharmony_ci curr_freq = (((ioread32(&pcch_hdr->nominal) * (output_buffer & 0xff)) 1758c2ecf20Sopenharmony_ci / 100) * 1000); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci pr_debug("get: SUCCESS: (virtual) output_offset for cpu %d is " 1788c2ecf20Sopenharmony_ci "0x%p, contains a value of: 0x%x. Speed is: %d MHz\n", 1798c2ecf20Sopenharmony_ci cpu, (pcch_virt_addr + pcc_cpu_data->output_offset), 1808c2ecf20Sopenharmony_ci output_buffer, curr_freq); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci freq_limit = (output_buffer >> 8) & 0xff; 1838c2ecf20Sopenharmony_ci if (freq_limit != 0xff) { 1848c2ecf20Sopenharmony_ci pr_debug("get: frequency for cpu %d is being temporarily" 1858c2ecf20Sopenharmony_ci " capped at %d\n", cpu, curr_freq); 1868c2ecf20Sopenharmony_ci } 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci spin_unlock(&pcc_lock); 1898c2ecf20Sopenharmony_ci return curr_freq; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cicmd_incomplete: 1928c2ecf20Sopenharmony_ci iowrite16(0, &pcch_hdr->status); 1938c2ecf20Sopenharmony_ci spin_unlock(&pcc_lock); 1948c2ecf20Sopenharmony_ci return 0; 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic int pcc_cpufreq_target(struct cpufreq_policy *policy, 1988c2ecf20Sopenharmony_ci unsigned int target_freq, 1998c2ecf20Sopenharmony_ci unsigned int relation) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci struct pcc_cpu *pcc_cpu_data; 2028c2ecf20Sopenharmony_ci struct cpufreq_freqs freqs; 2038c2ecf20Sopenharmony_ci u16 status; 2048c2ecf20Sopenharmony_ci u32 input_buffer; 2058c2ecf20Sopenharmony_ci int cpu; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci cpu = policy->cpu; 2088c2ecf20Sopenharmony_ci pcc_cpu_data = per_cpu_ptr(pcc_cpu_info, cpu); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci pr_debug("target: CPU %d should go to target freq: %d " 2118c2ecf20Sopenharmony_ci "(virtual) input_offset is 0x%p\n", 2128c2ecf20Sopenharmony_ci cpu, target_freq, 2138c2ecf20Sopenharmony_ci (pcch_virt_addr + pcc_cpu_data->input_offset)); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci freqs.old = policy->cur; 2168c2ecf20Sopenharmony_ci freqs.new = target_freq; 2178c2ecf20Sopenharmony_ci cpufreq_freq_transition_begin(policy, &freqs); 2188c2ecf20Sopenharmony_ci spin_lock(&pcc_lock); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci input_buffer = 0x1 | (((target_freq * 100) 2218c2ecf20Sopenharmony_ci / (ioread32(&pcch_hdr->nominal) * 1000)) << 8); 2228c2ecf20Sopenharmony_ci iowrite32(input_buffer, 2238c2ecf20Sopenharmony_ci (pcch_virt_addr + pcc_cpu_data->input_offset)); 2248c2ecf20Sopenharmony_ci iowrite16(CMD_SET_FREQ, &pcch_hdr->command); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci pcc_cmd(); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci /* Clear the input buffer - we are done with the current command */ 2298c2ecf20Sopenharmony_ci memset_io((pcch_virt_addr + pcc_cpu_data->input_offset), 0, BUF_SZ); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci status = ioread16(&pcch_hdr->status); 2328c2ecf20Sopenharmony_ci iowrite16(0, &pcch_hdr->status); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci cpufreq_freq_transition_end(policy, &freqs, status != CMD_COMPLETE); 2358c2ecf20Sopenharmony_ci spin_unlock(&pcc_lock); 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci if (status != CMD_COMPLETE) { 2388c2ecf20Sopenharmony_ci pr_debug("target: FAILED for cpu %d, with status: 0x%x\n", 2398c2ecf20Sopenharmony_ci cpu, status); 2408c2ecf20Sopenharmony_ci return -EINVAL; 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci pr_debug("target: was SUCCESSFUL for cpu %d\n", cpu); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci return 0; 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_cistatic int pcc_get_offset(int cpu) 2498c2ecf20Sopenharmony_ci{ 2508c2ecf20Sopenharmony_ci acpi_status status; 2518c2ecf20Sopenharmony_ci struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; 2528c2ecf20Sopenharmony_ci union acpi_object *pccp, *offset; 2538c2ecf20Sopenharmony_ci struct pcc_cpu *pcc_cpu_data; 2548c2ecf20Sopenharmony_ci struct acpi_processor *pr; 2558c2ecf20Sopenharmony_ci int ret = 0; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci pr = per_cpu(processors, cpu); 2588c2ecf20Sopenharmony_ci pcc_cpu_data = per_cpu_ptr(pcc_cpu_info, cpu); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci if (!pr) 2618c2ecf20Sopenharmony_ci return -ENODEV; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci status = acpi_evaluate_object(pr->handle, "PCCP", NULL, &buffer); 2648c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 2658c2ecf20Sopenharmony_ci return -ENODEV; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci pccp = buffer.pointer; 2688c2ecf20Sopenharmony_ci if (!pccp || pccp->type != ACPI_TYPE_PACKAGE) { 2698c2ecf20Sopenharmony_ci ret = -ENODEV; 2708c2ecf20Sopenharmony_ci goto out_free; 2718c2ecf20Sopenharmony_ci } 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci offset = &(pccp->package.elements[0]); 2748c2ecf20Sopenharmony_ci if (!offset || offset->type != ACPI_TYPE_INTEGER) { 2758c2ecf20Sopenharmony_ci ret = -ENODEV; 2768c2ecf20Sopenharmony_ci goto out_free; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci pcc_cpu_data->input_offset = offset->integer.value; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci offset = &(pccp->package.elements[1]); 2828c2ecf20Sopenharmony_ci if (!offset || offset->type != ACPI_TYPE_INTEGER) { 2838c2ecf20Sopenharmony_ci ret = -ENODEV; 2848c2ecf20Sopenharmony_ci goto out_free; 2858c2ecf20Sopenharmony_ci } 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci pcc_cpu_data->output_offset = offset->integer.value; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci memset_io((pcch_virt_addr + pcc_cpu_data->input_offset), 0, BUF_SZ); 2908c2ecf20Sopenharmony_ci memset_io((pcch_virt_addr + pcc_cpu_data->output_offset), 0, BUF_SZ); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci pr_debug("pcc_get_offset: for CPU %d: pcc_cpu_data " 2938c2ecf20Sopenharmony_ci "input_offset: 0x%x, pcc_cpu_data output_offset: 0x%x\n", 2948c2ecf20Sopenharmony_ci cpu, pcc_cpu_data->input_offset, pcc_cpu_data->output_offset); 2958c2ecf20Sopenharmony_ciout_free: 2968c2ecf20Sopenharmony_ci kfree(buffer.pointer); 2978c2ecf20Sopenharmony_ci return ret; 2988c2ecf20Sopenharmony_ci} 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cistatic int __init pcc_cpufreq_do_osc(acpi_handle *handle) 3018c2ecf20Sopenharmony_ci{ 3028c2ecf20Sopenharmony_ci acpi_status status; 3038c2ecf20Sopenharmony_ci struct acpi_object_list input; 3048c2ecf20Sopenharmony_ci struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; 3058c2ecf20Sopenharmony_ci union acpi_object in_params[4]; 3068c2ecf20Sopenharmony_ci union acpi_object *out_obj; 3078c2ecf20Sopenharmony_ci u32 capabilities[2]; 3088c2ecf20Sopenharmony_ci u32 errors; 3098c2ecf20Sopenharmony_ci u32 supported; 3108c2ecf20Sopenharmony_ci int ret = 0; 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci input.count = 4; 3138c2ecf20Sopenharmony_ci input.pointer = in_params; 3148c2ecf20Sopenharmony_ci in_params[0].type = ACPI_TYPE_BUFFER; 3158c2ecf20Sopenharmony_ci in_params[0].buffer.length = 16; 3168c2ecf20Sopenharmony_ci in_params[0].buffer.pointer = OSC_UUID; 3178c2ecf20Sopenharmony_ci in_params[1].type = ACPI_TYPE_INTEGER; 3188c2ecf20Sopenharmony_ci in_params[1].integer.value = 1; 3198c2ecf20Sopenharmony_ci in_params[2].type = ACPI_TYPE_INTEGER; 3208c2ecf20Sopenharmony_ci in_params[2].integer.value = 2; 3218c2ecf20Sopenharmony_ci in_params[3].type = ACPI_TYPE_BUFFER; 3228c2ecf20Sopenharmony_ci in_params[3].buffer.length = 8; 3238c2ecf20Sopenharmony_ci in_params[3].buffer.pointer = (u8 *)&capabilities; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci capabilities[0] = OSC_QUERY_ENABLE; 3268c2ecf20Sopenharmony_ci capabilities[1] = 0x1; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci status = acpi_evaluate_object(*handle, "_OSC", &input, &output); 3298c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 3308c2ecf20Sopenharmony_ci return -ENODEV; 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci if (!output.length) 3338c2ecf20Sopenharmony_ci return -ENODEV; 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci out_obj = output.pointer; 3368c2ecf20Sopenharmony_ci if (out_obj->type != ACPI_TYPE_BUFFER) { 3378c2ecf20Sopenharmony_ci ret = -ENODEV; 3388c2ecf20Sopenharmony_ci goto out_free; 3398c2ecf20Sopenharmony_ci } 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci errors = *((u32 *)out_obj->buffer.pointer) & ~(1 << 0); 3428c2ecf20Sopenharmony_ci if (errors) { 3438c2ecf20Sopenharmony_ci ret = -ENODEV; 3448c2ecf20Sopenharmony_ci goto out_free; 3458c2ecf20Sopenharmony_ci } 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci supported = *((u32 *)(out_obj->buffer.pointer + 4)); 3488c2ecf20Sopenharmony_ci if (!(supported & 0x1)) { 3498c2ecf20Sopenharmony_ci ret = -ENODEV; 3508c2ecf20Sopenharmony_ci goto out_free; 3518c2ecf20Sopenharmony_ci } 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci kfree(output.pointer); 3548c2ecf20Sopenharmony_ci capabilities[0] = 0x0; 3558c2ecf20Sopenharmony_ci capabilities[1] = 0x1; 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci status = acpi_evaluate_object(*handle, "_OSC", &input, &output); 3588c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 3598c2ecf20Sopenharmony_ci return -ENODEV; 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci if (!output.length) 3628c2ecf20Sopenharmony_ci return -ENODEV; 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci out_obj = output.pointer; 3658c2ecf20Sopenharmony_ci if (out_obj->type != ACPI_TYPE_BUFFER) { 3668c2ecf20Sopenharmony_ci ret = -ENODEV; 3678c2ecf20Sopenharmony_ci goto out_free; 3688c2ecf20Sopenharmony_ci } 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci errors = *((u32 *)out_obj->buffer.pointer) & ~(1 << 0); 3718c2ecf20Sopenharmony_ci if (errors) { 3728c2ecf20Sopenharmony_ci ret = -ENODEV; 3738c2ecf20Sopenharmony_ci goto out_free; 3748c2ecf20Sopenharmony_ci } 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci supported = *((u32 *)(out_obj->buffer.pointer + 4)); 3778c2ecf20Sopenharmony_ci if (!(supported & 0x1)) { 3788c2ecf20Sopenharmony_ci ret = -ENODEV; 3798c2ecf20Sopenharmony_ci goto out_free; 3808c2ecf20Sopenharmony_ci } 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ciout_free: 3838c2ecf20Sopenharmony_ci kfree(output.pointer); 3848c2ecf20Sopenharmony_ci return ret; 3858c2ecf20Sopenharmony_ci} 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_cistatic int __init pcc_cpufreq_probe(void) 3888c2ecf20Sopenharmony_ci{ 3898c2ecf20Sopenharmony_ci acpi_status status; 3908c2ecf20Sopenharmony_ci struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; 3918c2ecf20Sopenharmony_ci struct pcc_memory_resource *mem_resource; 3928c2ecf20Sopenharmony_ci struct pcc_register_resource *reg_resource; 3938c2ecf20Sopenharmony_ci union acpi_object *out_obj, *member; 3948c2ecf20Sopenharmony_ci acpi_handle handle, osc_handle; 3958c2ecf20Sopenharmony_ci int ret = 0; 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci status = acpi_get_handle(NULL, "\\_SB", &handle); 3988c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 3998c2ecf20Sopenharmony_ci return -ENODEV; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci if (!acpi_has_method(handle, "PCCH")) 4028c2ecf20Sopenharmony_ci return -ENODEV; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci status = acpi_get_handle(handle, "_OSC", &osc_handle); 4058c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(status)) { 4068c2ecf20Sopenharmony_ci ret = pcc_cpufreq_do_osc(&osc_handle); 4078c2ecf20Sopenharmony_ci if (ret) 4088c2ecf20Sopenharmony_ci pr_debug("probe: _OSC evaluation did not succeed\n"); 4098c2ecf20Sopenharmony_ci /* Firmware's use of _OSC is optional */ 4108c2ecf20Sopenharmony_ci ret = 0; 4118c2ecf20Sopenharmony_ci } 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci status = acpi_evaluate_object(handle, "PCCH", NULL, &output); 4148c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 4158c2ecf20Sopenharmony_ci return -ENODEV; 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_ci out_obj = output.pointer; 4188c2ecf20Sopenharmony_ci if (out_obj->type != ACPI_TYPE_PACKAGE) { 4198c2ecf20Sopenharmony_ci ret = -ENODEV; 4208c2ecf20Sopenharmony_ci goto out_free; 4218c2ecf20Sopenharmony_ci } 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci member = &out_obj->package.elements[0]; 4248c2ecf20Sopenharmony_ci if (member->type != ACPI_TYPE_BUFFER) { 4258c2ecf20Sopenharmony_ci ret = -ENODEV; 4268c2ecf20Sopenharmony_ci goto out_free; 4278c2ecf20Sopenharmony_ci } 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci mem_resource = (struct pcc_memory_resource *)member->buffer.pointer; 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci pr_debug("probe: mem_resource descriptor: 0x%x," 4328c2ecf20Sopenharmony_ci " length: %d, space_id: %d, resource_usage: %d," 4338c2ecf20Sopenharmony_ci " type_specific: %d, granularity: 0x%llx," 4348c2ecf20Sopenharmony_ci " minimum: 0x%llx, maximum: 0x%llx," 4358c2ecf20Sopenharmony_ci " translation_offset: 0x%llx, address_length: 0x%llx\n", 4368c2ecf20Sopenharmony_ci mem_resource->descriptor, mem_resource->length, 4378c2ecf20Sopenharmony_ci mem_resource->space_id, mem_resource->resource_usage, 4388c2ecf20Sopenharmony_ci mem_resource->type_specific, mem_resource->granularity, 4398c2ecf20Sopenharmony_ci mem_resource->minimum, mem_resource->maximum, 4408c2ecf20Sopenharmony_ci mem_resource->translation_offset, 4418c2ecf20Sopenharmony_ci mem_resource->address_length); 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci if (mem_resource->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) { 4448c2ecf20Sopenharmony_ci ret = -ENODEV; 4458c2ecf20Sopenharmony_ci goto out_free; 4468c2ecf20Sopenharmony_ci } 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci pcch_virt_addr = ioremap(mem_resource->minimum, 4498c2ecf20Sopenharmony_ci mem_resource->address_length); 4508c2ecf20Sopenharmony_ci if (pcch_virt_addr == NULL) { 4518c2ecf20Sopenharmony_ci pr_debug("probe: could not map shared mem region\n"); 4528c2ecf20Sopenharmony_ci ret = -ENOMEM; 4538c2ecf20Sopenharmony_ci goto out_free; 4548c2ecf20Sopenharmony_ci } 4558c2ecf20Sopenharmony_ci pcch_hdr = pcch_virt_addr; 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci pr_debug("probe: PCCH header (virtual) addr: 0x%p\n", pcch_hdr); 4588c2ecf20Sopenharmony_ci pr_debug("probe: PCCH header is at physical address: 0x%llx," 4598c2ecf20Sopenharmony_ci " signature: 0x%x, length: %d bytes, major: %d, minor: %d," 4608c2ecf20Sopenharmony_ci " supported features: 0x%x, command field: 0x%x," 4618c2ecf20Sopenharmony_ci " status field: 0x%x, nominal latency: %d us\n", 4628c2ecf20Sopenharmony_ci mem_resource->minimum, ioread32(&pcch_hdr->signature), 4638c2ecf20Sopenharmony_ci ioread16(&pcch_hdr->length), ioread8(&pcch_hdr->major), 4648c2ecf20Sopenharmony_ci ioread8(&pcch_hdr->minor), ioread32(&pcch_hdr->features), 4658c2ecf20Sopenharmony_ci ioread16(&pcch_hdr->command), ioread16(&pcch_hdr->status), 4668c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->latency)); 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci pr_debug("probe: min time between commands: %d us," 4698c2ecf20Sopenharmony_ci " max time between commands: %d us," 4708c2ecf20Sopenharmony_ci " nominal CPU frequency: %d MHz," 4718c2ecf20Sopenharmony_ci " minimum CPU frequency: %d MHz," 4728c2ecf20Sopenharmony_ci " minimum CPU frequency without throttling: %d MHz\n", 4738c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->minimum_time), 4748c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->maximum_time), 4758c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->nominal), 4768c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->throttled_frequency), 4778c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->minimum_frequency)); 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci member = &out_obj->package.elements[1]; 4808c2ecf20Sopenharmony_ci if (member->type != ACPI_TYPE_BUFFER) { 4818c2ecf20Sopenharmony_ci ret = -ENODEV; 4828c2ecf20Sopenharmony_ci goto pcch_free; 4838c2ecf20Sopenharmony_ci } 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci reg_resource = (struct pcc_register_resource *)member->buffer.pointer; 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci doorbell.space_id = reg_resource->space_id; 4888c2ecf20Sopenharmony_ci doorbell.bit_width = reg_resource->bit_width; 4898c2ecf20Sopenharmony_ci doorbell.bit_offset = reg_resource->bit_offset; 4908c2ecf20Sopenharmony_ci doorbell.access_width = 4; 4918c2ecf20Sopenharmony_ci doorbell.address = reg_resource->address; 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci pr_debug("probe: doorbell: space_id is %d, bit_width is %d, " 4948c2ecf20Sopenharmony_ci "bit_offset is %d, access_width is %d, address is 0x%llx\n", 4958c2ecf20Sopenharmony_ci doorbell.space_id, doorbell.bit_width, doorbell.bit_offset, 4968c2ecf20Sopenharmony_ci doorbell.access_width, reg_resource->address); 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci member = &out_obj->package.elements[2]; 4998c2ecf20Sopenharmony_ci if (member->type != ACPI_TYPE_INTEGER) { 5008c2ecf20Sopenharmony_ci ret = -ENODEV; 5018c2ecf20Sopenharmony_ci goto pcch_free; 5028c2ecf20Sopenharmony_ci } 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci doorbell_preserve = member->integer.value; 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci member = &out_obj->package.elements[3]; 5078c2ecf20Sopenharmony_ci if (member->type != ACPI_TYPE_INTEGER) { 5088c2ecf20Sopenharmony_ci ret = -ENODEV; 5098c2ecf20Sopenharmony_ci goto pcch_free; 5108c2ecf20Sopenharmony_ci } 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci doorbell_write = member->integer.value; 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci pr_debug("probe: doorbell_preserve: 0x%llx," 5158c2ecf20Sopenharmony_ci " doorbell_write: 0x%llx\n", 5168c2ecf20Sopenharmony_ci doorbell_preserve, doorbell_write); 5178c2ecf20Sopenharmony_ci 5188c2ecf20Sopenharmony_ci pcc_cpu_info = alloc_percpu(struct pcc_cpu); 5198c2ecf20Sopenharmony_ci if (!pcc_cpu_info) { 5208c2ecf20Sopenharmony_ci ret = -ENOMEM; 5218c2ecf20Sopenharmony_ci goto pcch_free; 5228c2ecf20Sopenharmony_ci } 5238c2ecf20Sopenharmony_ci 5248c2ecf20Sopenharmony_ci printk(KERN_DEBUG "pcc-cpufreq: (v%s) driver loaded with frequency" 5258c2ecf20Sopenharmony_ci " limits: %d MHz, %d MHz\n", PCC_VERSION, 5268c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->minimum_frequency), 5278c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->nominal)); 5288c2ecf20Sopenharmony_ci kfree(output.pointer); 5298c2ecf20Sopenharmony_ci return ret; 5308c2ecf20Sopenharmony_cipcch_free: 5318c2ecf20Sopenharmony_ci pcc_clear_mapping(); 5328c2ecf20Sopenharmony_ciout_free: 5338c2ecf20Sopenharmony_ci kfree(output.pointer); 5348c2ecf20Sopenharmony_ci return ret; 5358c2ecf20Sopenharmony_ci} 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_cistatic int pcc_cpufreq_cpu_init(struct cpufreq_policy *policy) 5388c2ecf20Sopenharmony_ci{ 5398c2ecf20Sopenharmony_ci unsigned int cpu = policy->cpu; 5408c2ecf20Sopenharmony_ci unsigned int result = 0; 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_ci if (!pcch_virt_addr) { 5438c2ecf20Sopenharmony_ci result = -1; 5448c2ecf20Sopenharmony_ci goto out; 5458c2ecf20Sopenharmony_ci } 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci result = pcc_get_offset(cpu); 5488c2ecf20Sopenharmony_ci if (result) { 5498c2ecf20Sopenharmony_ci pr_debug("init: PCCP evaluation failed\n"); 5508c2ecf20Sopenharmony_ci goto out; 5518c2ecf20Sopenharmony_ci } 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci policy->max = policy->cpuinfo.max_freq = 5548c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->nominal) * 1000; 5558c2ecf20Sopenharmony_ci policy->min = policy->cpuinfo.min_freq = 5568c2ecf20Sopenharmony_ci ioread32(&pcch_hdr->minimum_frequency) * 1000; 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci pr_debug("init: policy->max is %d, policy->min is %d\n", 5598c2ecf20Sopenharmony_ci policy->max, policy->min); 5608c2ecf20Sopenharmony_ciout: 5618c2ecf20Sopenharmony_ci return result; 5628c2ecf20Sopenharmony_ci} 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_cistatic int pcc_cpufreq_cpu_exit(struct cpufreq_policy *policy) 5658c2ecf20Sopenharmony_ci{ 5668c2ecf20Sopenharmony_ci return 0; 5678c2ecf20Sopenharmony_ci} 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_cistatic struct cpufreq_driver pcc_cpufreq_driver = { 5708c2ecf20Sopenharmony_ci .flags = CPUFREQ_CONST_LOOPS, 5718c2ecf20Sopenharmony_ci .get = pcc_get_freq, 5728c2ecf20Sopenharmony_ci .verify = pcc_cpufreq_verify, 5738c2ecf20Sopenharmony_ci .target = pcc_cpufreq_target, 5748c2ecf20Sopenharmony_ci .init = pcc_cpufreq_cpu_init, 5758c2ecf20Sopenharmony_ci .exit = pcc_cpufreq_cpu_exit, 5768c2ecf20Sopenharmony_ci .name = "pcc-cpufreq", 5778c2ecf20Sopenharmony_ci}; 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_cistatic int __init pcc_cpufreq_init(void) 5808c2ecf20Sopenharmony_ci{ 5818c2ecf20Sopenharmony_ci int ret; 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_ci /* Skip initialization if another cpufreq driver is there. */ 5848c2ecf20Sopenharmony_ci if (cpufreq_get_current_driver()) 5858c2ecf20Sopenharmony_ci return -EEXIST; 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ci if (acpi_disabled) 5888c2ecf20Sopenharmony_ci return -ENODEV; 5898c2ecf20Sopenharmony_ci 5908c2ecf20Sopenharmony_ci ret = pcc_cpufreq_probe(); 5918c2ecf20Sopenharmony_ci if (ret) { 5928c2ecf20Sopenharmony_ci pr_debug("pcc_cpufreq_init: PCCH evaluation failed\n"); 5938c2ecf20Sopenharmony_ci return ret; 5948c2ecf20Sopenharmony_ci } 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci if (num_present_cpus() > 4) { 5978c2ecf20Sopenharmony_ci pcc_cpufreq_driver.flags |= CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING; 5988c2ecf20Sopenharmony_ci pr_err("%s: Too many CPUs, dynamic performance scaling disabled\n", 5998c2ecf20Sopenharmony_ci __func__); 6008c2ecf20Sopenharmony_ci pr_err("%s: Try to enable another scaling driver through BIOS settings\n", 6018c2ecf20Sopenharmony_ci __func__); 6028c2ecf20Sopenharmony_ci pr_err("%s: and complain to the system vendor\n", __func__); 6038c2ecf20Sopenharmony_ci } 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_ci ret = cpufreq_register_driver(&pcc_cpufreq_driver); 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci return ret; 6088c2ecf20Sopenharmony_ci} 6098c2ecf20Sopenharmony_ci 6108c2ecf20Sopenharmony_cistatic void __exit pcc_cpufreq_exit(void) 6118c2ecf20Sopenharmony_ci{ 6128c2ecf20Sopenharmony_ci cpufreq_unregister_driver(&pcc_cpufreq_driver); 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci pcc_clear_mapping(); 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_ci free_percpu(pcc_cpu_info); 6178c2ecf20Sopenharmony_ci} 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_cistatic const struct acpi_device_id __maybe_unused processor_device_ids[] = { 6208c2ecf20Sopenharmony_ci {ACPI_PROCESSOR_OBJECT_HID, }, 6218c2ecf20Sopenharmony_ci {ACPI_PROCESSOR_DEVICE_HID, }, 6228c2ecf20Sopenharmony_ci {}, 6238c2ecf20Sopenharmony_ci}; 6248c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, processor_device_ids); 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Matthew Garrett, Naga Chumbalkar"); 6278c2ecf20Sopenharmony_ciMODULE_VERSION(PCC_VERSION); 6288c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Processor Clocking Control interface driver"); 6298c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 6308c2ecf20Sopenharmony_ci 6318c2ecf20Sopenharmony_cilate_initcall(pcc_cpufreq_init); 6328c2ecf20Sopenharmony_cimodule_exit(pcc_cpufreq_exit); 633