282 lines
8.2 KiB
C
282 lines
8.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* AMD Platform Management Framework Driver - Smart PC Capabilities
|
|
*
|
|
* Copyright (c) 2023, Advanced Micro Devices, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
|
* Patil Rajesh Reddy <Patil.Reddy@amd.com>
|
|
*/
|
|
|
|
#include <acpi/button.h>
|
|
#include <linux/amd-pmf-io.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/units.h>
|
|
#include "pmf.h"
|
|
|
|
#ifdef CONFIG_AMD_PMF_DEBUG
|
|
static const char *platform_type_as_str(u16 platform_type)
|
|
{
|
|
switch (platform_type) {
|
|
case CLAMSHELL:
|
|
return "CLAMSHELL";
|
|
case FLAT:
|
|
return "FLAT";
|
|
case TENT:
|
|
return "TENT";
|
|
case STAND:
|
|
return "STAND";
|
|
case TABLET:
|
|
return "TABLET";
|
|
case BOOK:
|
|
return "BOOK";
|
|
case PRESENTATION:
|
|
return "PRESENTATION";
|
|
case PULL_FWD:
|
|
return "PULL_FWD";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static const char *laptop_placement_as_str(u16 device_state)
|
|
{
|
|
switch (device_state) {
|
|
case ON_TABLE:
|
|
return "ON_TABLE";
|
|
case ON_LAP_MOTION:
|
|
return "ON_LAP_MOTION";
|
|
case IN_BAG:
|
|
return "IN_BAG";
|
|
case OUT_OF_BAG:
|
|
return "OUT_OF_BAG";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static const char *ta_slider_as_str(unsigned int state)
|
|
{
|
|
switch (state) {
|
|
case TA_BEST_PERFORMANCE:
|
|
return "PERFORMANCE";
|
|
case TA_BETTER_PERFORMANCE:
|
|
return "BALANCED";
|
|
case TA_BEST_BATTERY:
|
|
return "POWER_SAVER";
|
|
default:
|
|
return "Unknown TA Slider State";
|
|
}
|
|
}
|
|
|
|
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
|
{
|
|
dev_dbg(dev->dev, "==== TA inputs START ====\n");
|
|
dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider));
|
|
dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source));
|
|
dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage);
|
|
dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design);
|
|
dev_dbg(dev->dev, "Fully Charged Capacity: %u\n", in->ev_info.full_charge_capacity);
|
|
dev_dbg(dev->dev, "Drain Rate: %d\n", in->ev_info.drain_rate);
|
|
dev_dbg(dev->dev, "Socket Power: %u\n", in->ev_info.socket_power);
|
|
dev_dbg(dev->dev, "Skin Temperature: %u\n", in->ev_info.skin_temperature);
|
|
dev_dbg(dev->dev, "Avg C0 Residency: %u\n", in->ev_info.avg_c0residency);
|
|
dev_dbg(dev->dev, "Max C0 Residency: %u\n", in->ev_info.max_c0residency);
|
|
dev_dbg(dev->dev, "GFX Busy: %u\n", in->ev_info.gfx_busy);
|
|
dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open");
|
|
dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away");
|
|
dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light);
|
|
dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type));
|
|
dev_dbg(dev->dev, "Laptop placement: %s\n",
|
|
laptop_placement_as_str(in->ev_info.device_state));
|
|
dev_dbg(dev->dev, "Custom BIOS input1: %u\n", in->ev_info.bios_input1);
|
|
dev_dbg(dev->dev, "Custom BIOS input2: %u\n", in->ev_info.bios_input2);
|
|
dev_dbg(dev->dev, "==== TA inputs END ====\n");
|
|
}
|
|
#else
|
|
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
|
|
#endif
|
|
|
|
static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
|
|
struct ta_pmf_enact_table *in)
|
|
{
|
|
if (!pdev->req.pending_req)
|
|
return;
|
|
|
|
switch (pdev->req.pending_req) {
|
|
case BIT(NOTIFY_CUSTOM_BIOS_INPUT1):
|
|
in->ev_info.bios_input1 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT1];
|
|
break;
|
|
case BIT(NOTIFY_CUSTOM_BIOS_INPUT2):
|
|
in->ev_info.bios_input2 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT2];
|
|
break;
|
|
default:
|
|
dev_dbg(pdev->dev, "Invalid preq for BIOS input: 0x%x\n", pdev->req.pending_req);
|
|
}
|
|
|
|
/* Clear pending requests after handling */
|
|
memset(&pdev->req, 0, sizeof(pdev->req));
|
|
}
|
|
|
|
static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
|
|
{
|
|
u16 max, avg = 0;
|
|
int i;
|
|
|
|
/* Get the avg and max C0 residency of all the cores */
|
|
max = *core_res;
|
|
for (i = 0; i < size; i++) {
|
|
avg += core_res[i];
|
|
if (core_res[i] > max)
|
|
max = core_res[i];
|
|
}
|
|
avg = DIV_ROUND_CLOSEST(avg, size);
|
|
in->ev_info.avg_c0residency = avg;
|
|
in->ev_info.max_c0residency = max;
|
|
}
|
|
|
|
static void amd_pmf_get_smu_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
|
{
|
|
/* Get the updated metrics table data */
|
|
memset(dev->buf, 0, dev->mtable_size);
|
|
amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, 0, 7, NULL);
|
|
|
|
switch (dev->cpu_id) {
|
|
case AMD_CPU_ID_PS:
|
|
memcpy(&dev->m_table, dev->buf, dev->mtable_size);
|
|
in->ev_info.socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
|
|
in->ev_info.skin_temperature = dev->m_table.skin_temp;
|
|
in->ev_info.gfx_busy = dev->m_table.avg_gfx_activity;
|
|
amd_pmf_get_c0_residency(dev->m_table.avg_core_c0residency,
|
|
ARRAY_SIZE(dev->m_table.avg_core_c0residency), in);
|
|
break;
|
|
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
|
|
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
|
|
memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
|
|
in->ev_info.socket_power = dev->m_table_v2.apu_power + dev->m_table_v2.dgpu_power;
|
|
in->ev_info.skin_temperature = dev->m_table_v2.skin_temp;
|
|
in->ev_info.gfx_busy = dev->m_table_v2.gfx_activity;
|
|
amd_pmf_get_c0_residency(dev->m_table_v2.core_c0residency,
|
|
ARRAY_SIZE(dev->m_table_v2.core_c0residency), in);
|
|
break;
|
|
default:
|
|
dev_err(dev->dev, "Unsupported CPU id: 0x%x", dev->cpu_id);
|
|
}
|
|
}
|
|
|
|
static const char * const pmf_battery_supply_name[] = {
|
|
"BATT",
|
|
"BAT0",
|
|
};
|
|
|
|
static int amd_pmf_get_battery_prop(enum power_supply_property prop)
|
|
{
|
|
union power_supply_propval value;
|
|
struct power_supply *psy;
|
|
int i, ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pmf_battery_supply_name); i++) {
|
|
psy = power_supply_get_by_name(pmf_battery_supply_name[i]);
|
|
if (!psy)
|
|
continue;
|
|
|
|
ret = power_supply_get_property(psy, prop, &value);
|
|
if (ret) {
|
|
power_supply_put(psy);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return value.intval;
|
|
}
|
|
|
|
static int amd_pmf_get_battery_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
|
{
|
|
int val;
|
|
|
|
val = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_PRESENT);
|
|
if (val < 0)
|
|
return val;
|
|
if (val != 1)
|
|
return -ENODEV;
|
|
|
|
in->ev_info.bat_percentage = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_CAPACITY);
|
|
/* all values in mWh metrics */
|
|
in->ev_info.bat_design = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN) /
|
|
MILLIWATT_PER_WATT;
|
|
in->ev_info.full_charge_capacity = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_ENERGY_FULL) /
|
|
MILLIWATT_PER_WATT;
|
|
in->ev_info.drain_rate = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_POWER_NOW) /
|
|
MILLIWATT_PER_WATT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
|
{
|
|
int val;
|
|
|
|
switch (dev->current_profile) {
|
|
case PLATFORM_PROFILE_PERFORMANCE:
|
|
case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
|
|
val = TA_BEST_PERFORMANCE;
|
|
break;
|
|
case PLATFORM_PROFILE_BALANCED:
|
|
val = TA_BETTER_PERFORMANCE;
|
|
break;
|
|
case PLATFORM_PROFILE_LOW_POWER:
|
|
case PLATFORM_PROFILE_QUIET:
|
|
val = TA_BEST_BATTERY;
|
|
break;
|
|
default:
|
|
dev_err(dev->dev, "Unknown Platform Profile.\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
in->ev_info.power_slider = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void amd_pmf_get_sensor_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
|
{
|
|
struct amd_sfh_info sfh_info;
|
|
|
|
/* Get the latest information from SFH */
|
|
in->ev_info.user_present = false;
|
|
|
|
/* Get ALS data */
|
|
if (!amd_get_sfh_info(&sfh_info, MT_ALS))
|
|
in->ev_info.ambient_light = sfh_info.ambient_light;
|
|
else
|
|
dev_dbg(dev->dev, "ALS is not enabled/detected\n");
|
|
|
|
/* get HPD data */
|
|
if (!amd_get_sfh_info(&sfh_info, MT_HPD)) {
|
|
if (sfh_info.user_present == SFH_USER_PRESENT)
|
|
in->ev_info.user_present = true;
|
|
} else {
|
|
dev_dbg(dev->dev, "HPD is not enabled/detected\n");
|
|
}
|
|
|
|
/* Get SRA (Secondary Accelerometer) data */
|
|
if (!amd_get_sfh_info(&sfh_info, MT_SRA)) {
|
|
in->ev_info.platform_type = sfh_info.platform_type;
|
|
in->ev_info.device_state = sfh_info.laptop_placement;
|
|
} else {
|
|
dev_dbg(dev->dev, "SRA is not enabled/detected\n");
|
|
}
|
|
}
|
|
|
|
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
|
{
|
|
/* TA side lid open is 1 and close is 0, hence the ! here */
|
|
in->ev_info.lid_state = !acpi_lid_open();
|
|
in->ev_info.power_source = amd_pmf_get_power_source();
|
|
amd_pmf_get_smu_info(dev, in);
|
|
amd_pmf_get_battery_info(dev, in);
|
|
amd_pmf_get_slider_info(dev, in);
|
|
amd_pmf_get_sensor_info(dev, in);
|
|
amd_pmf_get_custom_bios_inputs(dev, in);
|
|
}
|