root/drivers/platform/x86/amd/pmf/spc.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/cleanup.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";
        }
}

static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
{
        switch (index) {
        case 0 ... 1:
                return in->ev_info.bios_input_1[index];
        case 2 ... 9:
                return in->ev_info.bios_input_2[index - 2];
        default:
                return 0;
        }
}

void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
        int i;

        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));
        for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
                dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1,
                        amd_pmf_get_ta_custom_bios_inputs(in, i));
        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

/*
 * This helper function sets the appropriate BIOS input value in the TA enact
 * table based on the provided index. We need this approach because the custom
 * BIOS input array is not continuous, due to the existing TA structure layout.
 */
static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int index, u32 value)
{
        switch (index) {
        case 0 ... 1:
                in->ev_info.bios_input_1[index] = value;
                break;
        case 2 ... 9:
                in->ev_info.bios_input_2[index - 2] = value;
                break;
        default:
                return;
        }
}

static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, struct pmf_bios_input_entry *data,
                                       const struct amd_pmf_pb_bitmap *inputs,
                                       struct ta_pmf_enact_table *in)
{
        unsigned int i;

        for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) {
                if (!(data->preq & inputs[i].bit_mask))
                        continue;
                amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]);
                pdev->cb_prev.custom_bios_inputs[i] = data->val[i];
                dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]);
        }
}

static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
                                           struct ta_pmf_enact_table *in)
{
        struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf;
        unsigned int i;

        guard(mutex)(&pdev->cbi_mutex);

        for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
                amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]);

        if (CIRC_CNT(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0)
                return;

        /* If no active custom BIOS input pending request, do not consume further work */
        if (!rb->data[rb->tail].preq)
                goto out_rbadvance;

        if (!pdev->smart_pc_enabled)
                return;

        switch (pdev->pmf_if_version) {
        case PMF_IF_V1:
                if (!is_apmf_bios_input_notifications_supported(pdev))
                        return;
                amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in);
                break;
        case PMF_IF_V2:
                amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in);
                break;
        default:
                break;
        }

out_rbadvance:
        rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
}

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, SET_CMD, METRICS_TABLE_ID, 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);
}