diff options
Diffstat (limited to 'drivers/acpi/cppc_acpi.c')
-rw-r--r-- | drivers/acpi/cppc_acpi.c | 366 |
1 files changed, 276 insertions, 90 deletions
diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index b62c87b8ce4a..093675b1a1ff 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -100,6 +100,16 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); (cpc)->cpc_entry.reg.space_id == \ ACPI_ADR_SPACE_PLATFORM_COMM) +/* Check if a CPC register is in SystemMemory */ +#define CPC_IN_SYSTEM_MEMORY(cpc) ((cpc)->type == ACPI_TYPE_BUFFER && \ + (cpc)->cpc_entry.reg.space_id == \ + ACPI_ADR_SPACE_SYSTEM_MEMORY) + +/* Check if a CPC register is in SystemIo */ +#define CPC_IN_SYSTEM_IO(cpc) ((cpc)->type == ACPI_TYPE_BUFFER && \ + (cpc)->cpc_entry.reg.space_id == \ + ACPI_ADR_SPACE_SYSTEM_IO) + /* Evaluates to True if reg is a NULL register descriptor */ #define IS_NULL_REG(reg) ((reg)->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY && \ (reg)->address == 0 && \ @@ -118,6 +128,8 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); */ #define NUM_RETRIES 500ULL +#define OVER_16BTS_MASK ~0xFFFFULL + #define define_one_cppc_ro(_name) \ static struct kobj_attribute _name = \ __ATTR(_name, 0444, show_##_name, NULL) @@ -179,10 +191,11 @@ static struct attribute *cppc_attrs[] = { &lowest_freq.attr, NULL }; +ATTRIBUTE_GROUPS(cppc); static struct kobj_type cppc_ktype = { .sysfs_ops = &kobj_sysfs_ops, - .default_attrs = cppc_attrs, + .default_groups = cppc_groups, }; static int check_pcc_chan(int pcc_ss_id, bool chk_err_bit) @@ -302,7 +315,7 @@ static int send_pcc_cmd(int pcc_ss_id, u16 cmd) goto end; } - /* wait for completion and check for PCC errro bit */ + /* wait for completion and check for PCC error bit */ ret = check_pcc_chan(pcc_ss_id, true); if (pcc_ss_data->pcc_mrtt) @@ -411,7 +424,10 @@ bool acpi_cpc_valid(void) struct cpc_desc *cpc_ptr; int cpu; - for_each_possible_cpu(cpu) { + if (acpi_disabled) + return false; + + for_each_present_cpu(cpu) { cpc_ptr = per_cpu(cpc_desc_ptr, cpu); if (!cpc_ptr) return false; @@ -421,6 +437,24 @@ bool acpi_cpc_valid(void) } EXPORT_SYMBOL_GPL(acpi_cpc_valid); +bool cppc_allow_fast_switch(void) +{ + struct cpc_register_resource *desired_reg; + struct cpc_desc *cpc_ptr; + int cpu; + + for_each_possible_cpu(cpu) { + cpc_ptr = per_cpu(cpc_desc_ptr, cpu); + desired_reg = &cpc_ptr->cpc_regs[DESIRED_PERF]; + if (!CPC_IN_SYSTEM_MEMORY(desired_reg) && + !CPC_IN_SYSTEM_IO(desired_reg)) + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(cppc_allow_fast_switch); + /** * acpi_get_psd_map - Map the CPUs in the freq domain of a given cpu * @cpu: Find all CPUs that share a domain with cpu. @@ -547,6 +581,19 @@ bool __weak cpc_ffh_supported(void) } /** + * cpc_supported_by_cpu() - check if CPPC is supported by CPU + * + * Check if the architectural support for CPPC is present even + * if the _OSC hasn't prescribed it + * + * Return: true for supported, false for not supported + */ +bool __weak cpc_supported_by_cpu(void) +{ + return false; +} + +/** * pcc_data_alloc() - Allocate the pcc_data memory for pcc subspace * * Check and allocate the cppc_pcc_data memory. @@ -574,81 +621,37 @@ static int pcc_data_alloc(int pcc_ss_id) return 0; } -/* Check if CPPC revision + num_ent combination is supported */ -static bool is_cppc_supported(int revision, int num_ent) -{ - int expected_num_ent; - - switch (revision) { - case CPPC_V2_REV: - expected_num_ent = CPPC_V2_NUM_ENT; - break; - case CPPC_V3_REV: - expected_num_ent = CPPC_V3_NUM_ENT; - break; - default: - pr_debug("Firmware exports unsupported CPPC revision: %d\n", - revision); - return false; - } - - if (expected_num_ent != num_ent) { - pr_debug("Firmware exports %d entries. Expected: %d for CPPC rev:%d\n", - num_ent, expected_num_ent, revision); - return false; - } - - return true; -} - /* * An example CPC table looks like the following. * - * Name(_CPC, Package() - * { - * 17, - * NumEntries - * 1, - * // Revision - * ResourceTemplate(){Register(PCC, 32, 0, 0x120, 2)}, - * // Highest Performance - * ResourceTemplate(){Register(PCC, 32, 0, 0x124, 2)}, - * // Nominal Performance - * ResourceTemplate(){Register(PCC, 32, 0, 0x128, 2)}, - * // Lowest Nonlinear Performance - * ResourceTemplate(){Register(PCC, 32, 0, 0x12C, 2)}, - * // Lowest Performance - * ResourceTemplate(){Register(PCC, 32, 0, 0x130, 2)}, - * // Guaranteed Performance Register - * ResourceTemplate(){Register(PCC, 32, 0, 0x110, 2)}, - * // Desired Performance Register - * ResourceTemplate(){Register(SystemMemory, 0, 0, 0, 0)}, - * .. - * .. - * .. - * - * } + * Name (_CPC, Package() { + * 17, // NumEntries + * 1, // Revision + * ResourceTemplate() {Register(PCC, 32, 0, 0x120, 2)}, // Highest Performance + * ResourceTemplate() {Register(PCC, 32, 0, 0x124, 2)}, // Nominal Performance + * ResourceTemplate() {Register(PCC, 32, 0, 0x128, 2)}, // Lowest Nonlinear Performance + * ResourceTemplate() {Register(PCC, 32, 0, 0x12C, 2)}, // Lowest Performance + * ResourceTemplate() {Register(PCC, 32, 0, 0x130, 2)}, // Guaranteed Performance Register + * ResourceTemplate() {Register(PCC, 32, 0, 0x110, 2)}, // Desired Performance Register + * ResourceTemplate() {Register(SystemMemory, 0, 0, 0, 0)}, + * ... + * ... + * ... + * } * Each Register() encodes how to access that specific register. * e.g. a sample PCC entry has the following encoding: * - * Register ( - * PCC, - * AddressSpaceKeyword - * 8, - * //RegisterBitWidth - * 8, - * //RegisterBitOffset - * 0x30, - * //RegisterAddress - * 9 - * //AccessSize (subspace ID) - * 0 - * ) - * } + * Register ( + * PCC, // AddressSpaceKeyword + * 8, // RegisterBitWidth + * 8, // RegisterBitOffset + * 0x30, // RegisterAddress + * 9, // AccessSize (subspace ID) + * ) */ -#ifndef init_freq_invariance_cppc -static inline void init_freq_invariance_cppc(void) { } +#ifndef arch_init_invariance_cppc +static inline void arch_init_invariance_cppc(void) { } #endif /** @@ -668,7 +671,13 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) unsigned int num_ent, i, cpc_rev; int pcc_subspace_id = -1; acpi_status status; - int ret = -EFAULT; + int ret = -ENODATA; + + if (!osc_sb_cppc2_support_acked) { + pr_debug("CPPC v2 _OSC not acked\n"); + if (!cpc_supported_by_cpu()) + return -ENODEV; + } /* Parse the ACPI _CPC table for this CPU. */ status = acpi_evaluate_object_typed(handle, "_CPC", NULL, &output, @@ -690,26 +699,52 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) cpc_obj = &out_obj->package.elements[0]; if (cpc_obj->type == ACPI_TYPE_INTEGER) { num_ent = cpc_obj->integer.value; + if (num_ent <= 1) { + pr_debug("Unexpected _CPC NumEntries value (%d) for CPU:%d\n", + num_ent, pr->id); + goto out_free; + } } else { - pr_debug("Unexpected entry type(%d) for NumEntries\n", - cpc_obj->type); + pr_debug("Unexpected _CPC NumEntries entry type (%d) for CPU:%d\n", + cpc_obj->type, pr->id); goto out_free; } - cpc_ptr->num_entries = num_ent; /* Second entry should be revision. */ cpc_obj = &out_obj->package.elements[1]; if (cpc_obj->type == ACPI_TYPE_INTEGER) { cpc_rev = cpc_obj->integer.value; } else { - pr_debug("Unexpected entry type(%d) for Revision\n", - cpc_obj->type); + pr_debug("Unexpected _CPC Revision entry type (%d) for CPU:%d\n", + cpc_obj->type, pr->id); + goto out_free; + } + + if (cpc_rev < CPPC_V2_REV) { + pr_debug("Unsupported _CPC Revision (%d) for CPU:%d\n", cpc_rev, + pr->id); goto out_free; } - cpc_ptr->version = cpc_rev; - if (!is_cppc_supported(cpc_rev, num_ent)) + /* + * Disregard _CPC if the number of entries in the return pachage is not + * as expected, but support future revisions being proper supersets of + * the v3 and only causing more entries to be returned by _CPC. + */ + if ((cpc_rev == CPPC_V2_REV && num_ent != CPPC_V2_NUM_ENT) || + (cpc_rev == CPPC_V3_REV && num_ent != CPPC_V3_NUM_ENT) || + (cpc_rev > CPPC_V3_REV && num_ent <= CPPC_V3_NUM_ENT)) { + pr_debug("Unexpected number of _CPC return package entries (%d) for CPU:%d\n", + num_ent, pr->id); goto out_free; + } + if (cpc_rev > CPPC_V3_REV) { + num_ent = CPPC_V3_NUM_ENT; + cpc_rev = CPPC_V3_REV; + } + + cpc_ptr->num_entries = num_ent; + cpc_ptr->version = cpc_rev; /* Iterate through remaining entries in _CPC */ for (i = 2; i < num_ent; i++) { @@ -734,22 +769,52 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (pcc_data_alloc(pcc_subspace_id)) goto out_free; } else if (pcc_subspace_id != gas_t->access_width) { - pr_debug("Mismatched PCC ids.\n"); + pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n", + pr->id); goto out_free; } } else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { if (gas_t->address) { void __iomem *addr; + if (!osc_cpc_flexible_adr_space_confirmed) { + pr_debug("Flexible address space capability not supported\n"); + if (!cpc_supported_by_cpu()) + goto out_free; + } + addr = ioremap(gas_t->address, gas_t->bit_width/8); if (!addr) goto out_free; cpc_ptr->cpc_regs[i-2].sys_mem_vaddr = addr; } + } else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { + if (gas_t->access_width < 1 || gas_t->access_width > 3) { + /* + * 1 = 8-bit, 2 = 16-bit, and 3 = 32-bit. + * SystemIO doesn't implement 64-bit + * registers. + */ + pr_debug("Invalid access width %d for SystemIO register in _CPC\n", + gas_t->access_width); + goto out_free; + } + if (gas_t->address & OVER_16BTS_MASK) { + /* SystemIO registers use 16-bit integer addresses */ + pr_debug("Invalid IO port %llu for SystemIO register in _CPC\n", + gas_t->address); + goto out_free; + } + if (!osc_cpc_flexible_adr_space_confirmed) { + pr_debug("Flexible address space capability not supported\n"); + if (!cpc_supported_by_cpu()) + goto out_free; + } } else { if (gas_t->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE || !cpc_ffh_supported()) { - /* Support only PCC ,SYS MEM and FFH type regs */ - pr_debug("Unsupported register type: %d\n", gas_t->space_id); + /* Support only PCC, SystemMemory, SystemIO, and FFH type regs. */ + pr_debug("Unsupported register type (%d) in _CPC\n", + gas_t->space_id); goto out_free; } } @@ -757,7 +822,8 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) cpc_ptr->cpc_regs[i-2].type = ACPI_TYPE_BUFFER; memcpy(&cpc_ptr->cpc_regs[i-2].cpc_entry.reg, gas_t, sizeof(*gas_t)); } else { - pr_debug("Err in entry:%d in CPC table of CPU:%d\n", i, pr->id); + pr_debug("Invalid entry type (%d) in _CPC for CPU:%d\n", + i, pr->id); goto out_free; } } @@ -813,7 +879,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; } - init_freq_invariance_cppc(); + arch_init_invariance_cppc(); kfree(output.pointer); return 0; @@ -912,18 +978,33 @@ int __weak cpc_write_ffh(int cpunum, struct cpc_reg *reg, u64 val) static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) { - int ret_val = 0; void __iomem *vaddr = NULL; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cpc_reg *reg = ®_res->cpc_entry.reg; if (reg_res->type == ACPI_TYPE_INTEGER) { *val = reg_res->cpc_entry.int_value; - return ret_val; + return 0; } *val = 0; - if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id >= 0) + + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { + u32 width = 8 << (reg->access_width - 1); + u32 val_u32; + acpi_status status; + + status = acpi_os_read_port((acpi_io_address)reg->address, + &val_u32, width); + if (ACPI_FAILURE(status)) { + pr_debug("Error: Failed to read SystemIO port %llx\n", + reg->address); + return -EFAULT; + } + + *val = val_u32; + return 0; + } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id >= 0) vaddr = GET_PCC_VADDR(reg->address, pcc_ss_id); else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) vaddr = reg_res->sys_mem_vaddr; @@ -949,10 +1030,10 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) default: pr_debug("Error: Cannot read %u bit width from PCC for ss: %d\n", reg->bit_width, pcc_ss_id); - ret_val = -EFAULT; + return -EFAULT; } - return ret_val; + return 0; } static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) @@ -962,7 +1043,20 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cpc_reg *reg = ®_res->cpc_entry.reg; - if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id >= 0) + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { + u32 width = 8 << (reg->access_width - 1); + acpi_status status; + + status = acpi_os_write_port((acpi_io_address)reg->address, + (u32)val, width); + if (ACPI_FAILURE(status)) { + pr_debug("Error: Failed to write SystemIO port %llx\n", + reg->address); + return -EFAULT; + } + + return 0; + } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id >= 0) vaddr = GET_PCC_VADDR(reg->address, pcc_ss_id); else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) vaddr = reg_res->sys_mem_vaddr; @@ -1150,6 +1244,48 @@ out_err: EXPORT_SYMBOL_GPL(cppc_get_perf_caps); /** + * cppc_perf_ctrs_in_pcc - Check if any perf counters are in a PCC region. + * + * CPPC has flexibility about how CPU performance counters are accessed. + * One of the choices is PCC regions, which can have a high access latency. This + * routine allows callers of cppc_get_perf_ctrs() to know this ahead of time. + * + * Return: true if any of the counters are in PCC regions, false otherwise + */ +bool cppc_perf_ctrs_in_pcc(void) +{ + int cpu; + + for_each_present_cpu(cpu) { + struct cpc_register_resource *ref_perf_reg; + struct cpc_desc *cpc_desc; + + cpc_desc = per_cpu(cpc_desc_ptr, cpu); + + if (CPC_IN_PCC(&cpc_desc->cpc_regs[DELIVERED_CTR]) || + CPC_IN_PCC(&cpc_desc->cpc_regs[REFERENCE_CTR]) || + CPC_IN_PCC(&cpc_desc->cpc_regs[CTR_WRAP_TIME])) + return true; + + + ref_perf_reg = &cpc_desc->cpc_regs[REFERENCE_PERF]; + + /* + * If reference perf register is not supported then we should + * use the nominal perf value + */ + if (!CPC_SUPPORTED(ref_perf_reg)) + ref_perf_reg = &cpc_desc->cpc_regs[NOMINAL_PERF]; + + if (CPC_IN_PCC(ref_perf_reg)) + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(cppc_perf_ctrs_in_pcc); + +/** * cppc_get_perf_ctrs - Read a CPU's performance feedback counters. * @cpunum: CPU from which to read counters. * @perf_fb_ctrs: ptr to cppc_perf_fb_ctrs. See cppc_acpi.h @@ -1230,6 +1366,51 @@ out_err: EXPORT_SYMBOL_GPL(cppc_get_perf_ctrs); /** + * cppc_set_enable - Set to enable CPPC on the processor by writing the + * Continuous Performance Control package EnableRegister field. + * @cpu: CPU for which to enable CPPC register. + * @enable: 0 - disable, 1 - enable CPPC feature on the processor. + * + * Return: 0 for success, -ERRNO or -EIO otherwise. + */ +int cppc_set_enable(int cpu, bool enable) +{ + int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); + struct cpc_register_resource *enable_reg; + struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); + struct cppc_pcc_data *pcc_ss_data = NULL; + int ret = -EINVAL; + + if (!cpc_desc) { + pr_debug("No CPC descriptor for CPU:%d\n", cpu); + return -EINVAL; + } + + enable_reg = &cpc_desc->cpc_regs[ENABLE]; + + if (CPC_IN_PCC(enable_reg)) { + + if (pcc_ss_id < 0) + return -EIO; + + ret = cpc_write(cpu, enable_reg, enable); + if (ret) + return ret; + + pcc_ss_data = pcc_data[pcc_ss_id]; + + down_write(&pcc_ss_data->pcc_lock); + /* after writing CPC, transfer the ownership of PCC to platfrom */ + ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); + up_write(&pcc_ss_data->pcc_lock); + return ret; + } + + return cpc_write(cpu, enable_reg, enable); +} +EXPORT_SYMBOL_GPL(cppc_set_enable); + +/** * cppc_set_perf - Set a CPU's performance controls. * @cpu: CPU for which to set performance controls. * @perf_ctrls: ptr to cppc_perf_ctrls. See cppc_acpi.h @@ -1360,6 +1541,9 @@ EXPORT_SYMBOL_GPL(cppc_set_perf); * transition latency for performance change requests. The closest we have * is the timing information from the PCCT tables which provides the info * on the number and frequency of PCC commands the platform can handle. + * + * If desired_reg is in the SystemMemory or SystemIo ACPI address space, + * then assume there is no latency. */ unsigned int cppc_get_transition_latency(int cpu_num) { @@ -1385,7 +1569,9 @@ unsigned int cppc_get_transition_latency(int cpu_num) return CPUFREQ_ETERNAL; desired_reg = &cpc_desc->cpc_regs[DESIRED_PERF]; - if (!CPC_IN_PCC(desired_reg)) + if (CPC_IN_SYSTEM_MEMORY(desired_reg) || CPC_IN_SYSTEM_IO(desired_reg)) + return 0; + else if (!CPC_IN_PCC(desired_reg)) return CPUFREQ_ETERNAL; if (pcc_ss_id < 0) |