162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * intel_rapl_tpmi: Intel RAPL driver via TPMI interface 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2023, Intel Corporation. 662306a36Sopenharmony_ci * All Rights Reserved. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/auxiliary_bus.h> 1262306a36Sopenharmony_ci#include <linux/io.h> 1362306a36Sopenharmony_ci#include <linux/intel_tpmi.h> 1462306a36Sopenharmony_ci#include <linux/intel_rapl.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define TPMI_RAPL_VERSION 1 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* 1 header + 10 registers + 5 reserved. 8 bytes for each. */ 2162306a36Sopenharmony_ci#define TPMI_RAPL_DOMAIN_SIZE 128 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cienum tpmi_rapl_domain_type { 2462306a36Sopenharmony_ci TPMI_RAPL_DOMAIN_INVALID, 2562306a36Sopenharmony_ci TPMI_RAPL_DOMAIN_SYSTEM, 2662306a36Sopenharmony_ci TPMI_RAPL_DOMAIN_PACKAGE, 2762306a36Sopenharmony_ci TPMI_RAPL_DOMAIN_RESERVED, 2862306a36Sopenharmony_ci TPMI_RAPL_DOMAIN_MEMORY, 2962306a36Sopenharmony_ci TPMI_RAPL_DOMAIN_MAX, 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cienum tpmi_rapl_register { 3362306a36Sopenharmony_ci TPMI_RAPL_REG_HEADER, 3462306a36Sopenharmony_ci TPMI_RAPL_REG_UNIT, 3562306a36Sopenharmony_ci TPMI_RAPL_REG_PL1, 3662306a36Sopenharmony_ci TPMI_RAPL_REG_PL2, 3762306a36Sopenharmony_ci TPMI_RAPL_REG_PL3, 3862306a36Sopenharmony_ci TPMI_RAPL_REG_PL4, 3962306a36Sopenharmony_ci TPMI_RAPL_REG_RESERVED, 4062306a36Sopenharmony_ci TPMI_RAPL_REG_ENERGY_STATUS, 4162306a36Sopenharmony_ci TPMI_RAPL_REG_PERF_STATUS, 4262306a36Sopenharmony_ci TPMI_RAPL_REG_POWER_INFO, 4362306a36Sopenharmony_ci TPMI_RAPL_REG_DOMAIN_INFO, 4462306a36Sopenharmony_ci TPMI_RAPL_REG_INTERRUPT, 4562306a36Sopenharmony_ci TPMI_RAPL_REG_MAX = 15, 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct tpmi_rapl_package { 4962306a36Sopenharmony_ci struct rapl_if_priv priv; 5062306a36Sopenharmony_ci struct intel_tpmi_plat_info *tpmi_info; 5162306a36Sopenharmony_ci struct rapl_package *rp; 5262306a36Sopenharmony_ci void __iomem *base; 5362306a36Sopenharmony_ci struct list_head node; 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic LIST_HEAD(tpmi_rapl_packages); 5762306a36Sopenharmony_cistatic DEFINE_MUTEX(tpmi_rapl_lock); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic struct powercap_control_type *tpmi_control_type; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic int tpmi_rapl_read_raw(int id, struct reg_action *ra) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci if (!ra->reg.mmio) 6462306a36Sopenharmony_ci return -EINVAL; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci ra->value = readq(ra->reg.mmio); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci ra->value &= ra->mask; 6962306a36Sopenharmony_ci return 0; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic int tpmi_rapl_write_raw(int id, struct reg_action *ra) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci u64 val; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (!ra->reg.mmio) 7762306a36Sopenharmony_ci return -EINVAL; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci val = readq(ra->reg.mmio); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci val &= ~ra->mask; 8262306a36Sopenharmony_ci val |= ra->value; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci writeq(val, ra->reg.mmio); 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic struct tpmi_rapl_package *trp_alloc(int pkg_id) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct tpmi_rapl_package *trp; 9162306a36Sopenharmony_ci int ret; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci mutex_lock(&tpmi_rapl_lock); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci if (list_empty(&tpmi_rapl_packages)) { 9662306a36Sopenharmony_ci tpmi_control_type = powercap_register_control_type(NULL, "intel-rapl", NULL); 9762306a36Sopenharmony_ci if (IS_ERR(tpmi_control_type)) { 9862306a36Sopenharmony_ci ret = PTR_ERR(tpmi_control_type); 9962306a36Sopenharmony_ci goto err_unlock; 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci trp = kzalloc(sizeof(*trp), GFP_KERNEL); 10462306a36Sopenharmony_ci if (!trp) { 10562306a36Sopenharmony_ci ret = -ENOMEM; 10662306a36Sopenharmony_ci goto err_del_powercap; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci list_add(&trp->node, &tpmi_rapl_packages); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci mutex_unlock(&tpmi_rapl_lock); 11262306a36Sopenharmony_ci return trp; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cierr_del_powercap: 11562306a36Sopenharmony_ci if (list_empty(&tpmi_rapl_packages)) 11662306a36Sopenharmony_ci powercap_unregister_control_type(tpmi_control_type); 11762306a36Sopenharmony_cierr_unlock: 11862306a36Sopenharmony_ci mutex_unlock(&tpmi_rapl_lock); 11962306a36Sopenharmony_ci return ERR_PTR(ret); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic void trp_release(struct tpmi_rapl_package *trp) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci mutex_lock(&tpmi_rapl_lock); 12562306a36Sopenharmony_ci list_del(&trp->node); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci if (list_empty(&tpmi_rapl_packages)) 12862306a36Sopenharmony_ci powercap_unregister_control_type(tpmi_control_type); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci kfree(trp); 13162306a36Sopenharmony_ci mutex_unlock(&tpmi_rapl_lock); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci/* 13562306a36Sopenharmony_ci * Bit 0 of TPMI_RAPL_REG_DOMAIN_INFO indicates if the current package is a domain 13662306a36Sopenharmony_ci * root or not. Only domain root packages can enumerate System (Psys) Domain. 13762306a36Sopenharmony_ci */ 13862306a36Sopenharmony_ci#define TPMI_RAPL_DOMAIN_ROOT BIT(0) 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic int parse_one_domain(struct tpmi_rapl_package *trp, u32 offset) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci u8 tpmi_domain_version; 14362306a36Sopenharmony_ci enum rapl_domain_type domain_type; 14462306a36Sopenharmony_ci enum tpmi_rapl_domain_type tpmi_domain_type; 14562306a36Sopenharmony_ci enum tpmi_rapl_register reg_index; 14662306a36Sopenharmony_ci enum rapl_domain_reg_id reg_id; 14762306a36Sopenharmony_ci int tpmi_domain_size, tpmi_domain_flags; 14862306a36Sopenharmony_ci u64 tpmi_domain_header = readq(trp->base + offset); 14962306a36Sopenharmony_ci u64 tpmi_domain_info; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* Domain Parent bits are ignored for now */ 15262306a36Sopenharmony_ci tpmi_domain_version = tpmi_domain_header & 0xff; 15362306a36Sopenharmony_ci tpmi_domain_type = tpmi_domain_header >> 8 & 0xff; 15462306a36Sopenharmony_ci tpmi_domain_size = tpmi_domain_header >> 16 & 0xff; 15562306a36Sopenharmony_ci tpmi_domain_flags = tpmi_domain_header >> 32 & 0xffff; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci if (tpmi_domain_version != TPMI_RAPL_VERSION) { 15862306a36Sopenharmony_ci pr_warn(FW_BUG "Unsupported version:%d\n", tpmi_domain_version); 15962306a36Sopenharmony_ci return -ENODEV; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* Domain size: in unit of 128 Bytes */ 16362306a36Sopenharmony_ci if (tpmi_domain_size != 1) { 16462306a36Sopenharmony_ci pr_warn(FW_BUG "Invalid Domain size %d\n", tpmi_domain_size); 16562306a36Sopenharmony_ci return -EINVAL; 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci /* Unit register and Energy Status register are mandatory for each domain */ 16962306a36Sopenharmony_ci if (!(tpmi_domain_flags & BIT(TPMI_RAPL_REG_UNIT)) || 17062306a36Sopenharmony_ci !(tpmi_domain_flags & BIT(TPMI_RAPL_REG_ENERGY_STATUS))) { 17162306a36Sopenharmony_ci pr_warn(FW_BUG "Invalid Domain flag 0x%x\n", tpmi_domain_flags); 17262306a36Sopenharmony_ci return -EINVAL; 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci switch (tpmi_domain_type) { 17662306a36Sopenharmony_ci case TPMI_RAPL_DOMAIN_PACKAGE: 17762306a36Sopenharmony_ci domain_type = RAPL_DOMAIN_PACKAGE; 17862306a36Sopenharmony_ci break; 17962306a36Sopenharmony_ci case TPMI_RAPL_DOMAIN_SYSTEM: 18062306a36Sopenharmony_ci if (!(tpmi_domain_flags & BIT(TPMI_RAPL_REG_DOMAIN_INFO))) { 18162306a36Sopenharmony_ci pr_warn(FW_BUG "System domain must support Domain Info register\n"); 18262306a36Sopenharmony_ci return -ENODEV; 18362306a36Sopenharmony_ci } 18462306a36Sopenharmony_ci tpmi_domain_info = readq(trp->base + offset + TPMI_RAPL_REG_DOMAIN_INFO); 18562306a36Sopenharmony_ci if (!(tpmi_domain_info & TPMI_RAPL_DOMAIN_ROOT)) 18662306a36Sopenharmony_ci return 0; 18762306a36Sopenharmony_ci domain_type = RAPL_DOMAIN_PLATFORM; 18862306a36Sopenharmony_ci break; 18962306a36Sopenharmony_ci case TPMI_RAPL_DOMAIN_MEMORY: 19062306a36Sopenharmony_ci domain_type = RAPL_DOMAIN_DRAM; 19162306a36Sopenharmony_ci break; 19262306a36Sopenharmony_ci default: 19362306a36Sopenharmony_ci pr_warn(FW_BUG "Unsupported Domain type %d\n", tpmi_domain_type); 19462306a36Sopenharmony_ci return -EINVAL; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (trp->priv.regs[domain_type][RAPL_DOMAIN_REG_UNIT].mmio) { 19862306a36Sopenharmony_ci pr_warn(FW_BUG "Duplicate Domain type %d\n", tpmi_domain_type); 19962306a36Sopenharmony_ci return -EINVAL; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci reg_index = TPMI_RAPL_REG_HEADER; 20362306a36Sopenharmony_ci while (++reg_index != TPMI_RAPL_REG_MAX) { 20462306a36Sopenharmony_ci if (!(tpmi_domain_flags & BIT(reg_index))) 20562306a36Sopenharmony_ci continue; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci switch (reg_index) { 20862306a36Sopenharmony_ci case TPMI_RAPL_REG_UNIT: 20962306a36Sopenharmony_ci reg_id = RAPL_DOMAIN_REG_UNIT; 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci case TPMI_RAPL_REG_PL1: 21262306a36Sopenharmony_ci reg_id = RAPL_DOMAIN_REG_LIMIT; 21362306a36Sopenharmony_ci trp->priv.limits[domain_type] |= BIT(POWER_LIMIT1); 21462306a36Sopenharmony_ci break; 21562306a36Sopenharmony_ci case TPMI_RAPL_REG_PL2: 21662306a36Sopenharmony_ci reg_id = RAPL_DOMAIN_REG_PL2; 21762306a36Sopenharmony_ci trp->priv.limits[domain_type] |= BIT(POWER_LIMIT2); 21862306a36Sopenharmony_ci break; 21962306a36Sopenharmony_ci case TPMI_RAPL_REG_PL4: 22062306a36Sopenharmony_ci reg_id = RAPL_DOMAIN_REG_PL4; 22162306a36Sopenharmony_ci trp->priv.limits[domain_type] |= BIT(POWER_LIMIT4); 22262306a36Sopenharmony_ci break; 22362306a36Sopenharmony_ci case TPMI_RAPL_REG_ENERGY_STATUS: 22462306a36Sopenharmony_ci reg_id = RAPL_DOMAIN_REG_STATUS; 22562306a36Sopenharmony_ci break; 22662306a36Sopenharmony_ci case TPMI_RAPL_REG_PERF_STATUS: 22762306a36Sopenharmony_ci reg_id = RAPL_DOMAIN_REG_PERF; 22862306a36Sopenharmony_ci break; 22962306a36Sopenharmony_ci case TPMI_RAPL_REG_POWER_INFO: 23062306a36Sopenharmony_ci reg_id = RAPL_DOMAIN_REG_INFO; 23162306a36Sopenharmony_ci break; 23262306a36Sopenharmony_ci default: 23362306a36Sopenharmony_ci continue; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci trp->priv.regs[domain_type][reg_id].mmio = trp->base + offset + reg_index * 8; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci return 0; 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic int intel_rapl_tpmi_probe(struct auxiliary_device *auxdev, 24262306a36Sopenharmony_ci const struct auxiliary_device_id *id) 24362306a36Sopenharmony_ci{ 24462306a36Sopenharmony_ci struct tpmi_rapl_package *trp; 24562306a36Sopenharmony_ci struct intel_tpmi_plat_info *info; 24662306a36Sopenharmony_ci struct resource *res; 24762306a36Sopenharmony_ci u32 offset; 24862306a36Sopenharmony_ci int ret; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci info = tpmi_get_platform_data(auxdev); 25162306a36Sopenharmony_ci if (!info) 25262306a36Sopenharmony_ci return -ENODEV; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci trp = trp_alloc(info->package_id); 25562306a36Sopenharmony_ci if (IS_ERR(trp)) 25662306a36Sopenharmony_ci return PTR_ERR(trp); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (tpmi_get_resource_count(auxdev) > 1) { 25962306a36Sopenharmony_ci dev_err(&auxdev->dev, "does not support multiple resources\n"); 26062306a36Sopenharmony_ci ret = -EINVAL; 26162306a36Sopenharmony_ci goto err; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci res = tpmi_get_resource_at_index(auxdev, 0); 26562306a36Sopenharmony_ci if (!res) { 26662306a36Sopenharmony_ci dev_err(&auxdev->dev, "can't fetch device resource info\n"); 26762306a36Sopenharmony_ci ret = -EIO; 26862306a36Sopenharmony_ci goto err; 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci trp->base = devm_ioremap_resource(&auxdev->dev, res); 27262306a36Sopenharmony_ci if (IS_ERR(trp->base)) { 27362306a36Sopenharmony_ci ret = PTR_ERR(trp->base); 27462306a36Sopenharmony_ci goto err; 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci for (offset = 0; offset < resource_size(res); offset += TPMI_RAPL_DOMAIN_SIZE) { 27862306a36Sopenharmony_ci ret = parse_one_domain(trp, offset); 27962306a36Sopenharmony_ci if (ret) 28062306a36Sopenharmony_ci goto err; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci trp->tpmi_info = info; 28462306a36Sopenharmony_ci trp->priv.type = RAPL_IF_TPMI; 28562306a36Sopenharmony_ci trp->priv.read_raw = tpmi_rapl_read_raw; 28662306a36Sopenharmony_ci trp->priv.write_raw = tpmi_rapl_write_raw; 28762306a36Sopenharmony_ci trp->priv.control_type = tpmi_control_type; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci /* RAPL TPMI I/F is per physical package */ 29062306a36Sopenharmony_ci trp->rp = rapl_find_package_domain(info->package_id, &trp->priv, false); 29162306a36Sopenharmony_ci if (trp->rp) { 29262306a36Sopenharmony_ci dev_err(&auxdev->dev, "Domain for Package%d already exists\n", info->package_id); 29362306a36Sopenharmony_ci ret = -EEXIST; 29462306a36Sopenharmony_ci goto err; 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci trp->rp = rapl_add_package(info->package_id, &trp->priv, false); 29862306a36Sopenharmony_ci if (IS_ERR(trp->rp)) { 29962306a36Sopenharmony_ci dev_err(&auxdev->dev, "Failed to add RAPL Domain for Package%d, %ld\n", 30062306a36Sopenharmony_ci info->package_id, PTR_ERR(trp->rp)); 30162306a36Sopenharmony_ci ret = PTR_ERR(trp->rp); 30262306a36Sopenharmony_ci goto err; 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci auxiliary_set_drvdata(auxdev, trp); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci return 0; 30862306a36Sopenharmony_cierr: 30962306a36Sopenharmony_ci trp_release(trp); 31062306a36Sopenharmony_ci return ret; 31162306a36Sopenharmony_ci} 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_cistatic void intel_rapl_tpmi_remove(struct auxiliary_device *auxdev) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci struct tpmi_rapl_package *trp = auxiliary_get_drvdata(auxdev); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci rapl_remove_package(trp->rp); 31862306a36Sopenharmony_ci trp_release(trp); 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic const struct auxiliary_device_id intel_rapl_tpmi_ids[] = { 32262306a36Sopenharmony_ci {.name = "intel_vsec.tpmi-rapl" }, 32362306a36Sopenharmony_ci { } 32462306a36Sopenharmony_ci}; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(auxiliary, intel_rapl_tpmi_ids); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic struct auxiliary_driver intel_rapl_tpmi_driver = { 32962306a36Sopenharmony_ci .probe = intel_rapl_tpmi_probe, 33062306a36Sopenharmony_ci .remove = intel_rapl_tpmi_remove, 33162306a36Sopenharmony_ci .id_table = intel_rapl_tpmi_ids, 33262306a36Sopenharmony_ci}; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_cimodule_auxiliary_driver(intel_rapl_tpmi_driver) 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ciMODULE_IMPORT_NS(INTEL_TPMI); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ciMODULE_DESCRIPTION("Intel RAPL TPMI Driver"); 33962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 340