mirror of
https://github.com/torvalds/linux.git
synced 2024-12-01 00:21:32 +00:00
Merge branches 'acpi-ec', 'acpi-cppc', 'acpi-fan' and 'acpi-battery'
Merge ACPI EC driver changes, CPPC-related changes, ACPI fan driver changes and ACPI battery driver changes for 5.18-rc1: - Make wakeup events checks in the ACPI EC driver more straightforward and clean up acpi_ec_submit_event() (Rafael Wysocki). - Make it possible to obtain the CPU capacity with the help of CPPC information (Ionela Voinescu). - Improve fine grained fan control in the ACPI fan driver and document it (Srinivas Pandruvada). - Add device HID and quirk for Microsoft Surface Go 3 to the ACPI battery driver (Maximilian Luz). * acpi-ec: ACPI: EC: Rearrange code in acpi_ec_submit_event() ACPI: EC: Reduce indentation level in acpi_ec_submit_event() ACPI: EC: Do not return result from advance_transaction() * acpi-cppc: arm64, topology: enable use of init_cpu_capacity_cppc() arch_topology: obtain cpu capacity using information from CPPC x86, ACPI: rename init_freq_invariance_cppc() to arch_init_invariance_cppc() * acpi-fan: Documentation/admin-guide/acpi: Add documentation for fine grain control ACPI: fan: Add additional attributes for fine grain control ACPI: fan: Properly handle fine grain control ACPI: fan: Optimize struct acpi_fan_fif ACPI: fan: Separate file for attributes creation ACPI: fan: Fix error reporting to user space * acpi-battery: ACPI: battery: Add device HID and quirk for Microsoft Surface Go 3
This commit is contained in:
commit
24b2b094b5
@ -60,3 +60,31 @@ For example::
|
||||
|
||||
When a given field is not populated or its value provided by the platform
|
||||
firmware is invalid, the "not-defined" string is shown instead of the value.
|
||||
|
||||
ACPI Fan Fine Grain Control
|
||||
=============================
|
||||
|
||||
When _FIF object specifies support for fine grain control, then fan speed
|
||||
can be set from 0 to 100% with the recommended minimum "step size" via
|
||||
_FSL object. User can adjust fan speed using thermal sysfs cooling device.
|
||||
|
||||
Here use can look at fan performance states for a reference speed (speed_rpm)
|
||||
and set it by changing cooling device cur_state. If the fine grain control
|
||||
is supported then user can also adjust to some other speeds which are
|
||||
not defined in the performance states.
|
||||
|
||||
The support of fine grain control is presented via sysfs attribute
|
||||
"fine_grain_control". If fine grain control is present, this attribute
|
||||
will show "1" otherwise "0".
|
||||
|
||||
This sysfs attribute is presented in the same directory as performance states.
|
||||
|
||||
ACPI Fan Performance Feedback
|
||||
=============================
|
||||
|
||||
The optional _FST object provides status information for the fan device.
|
||||
This includes field to provide current fan speed in revolutions per minute
|
||||
at which the fan is rotating.
|
||||
|
||||
This speed is presented in the sysfs using the attribute "fan_speed_rpm",
|
||||
in the same directory as performance states.
|
||||
|
@ -24,6 +24,10 @@ void update_freq_counters_refs(void);
|
||||
#define arch_scale_freq_capacity topology_get_freq_scale
|
||||
#define arch_scale_freq_invariant topology_scale_freq_invariant
|
||||
|
||||
#ifdef CONFIG_ACPI_CPPC_LIB
|
||||
#define arch_init_invariance_cppc topology_init_cpu_capacity_cppc
|
||||
#endif
|
||||
|
||||
/* Replace task scheduler's default cpu-invariant accounting */
|
||||
#define arch_scale_cpu_capacity topology_get_cpu_scale
|
||||
|
||||
|
@ -227,7 +227,7 @@ static inline void init_freq_invariance(bool secondary, bool cppc_ready)
|
||||
|
||||
#ifdef CONFIG_ACPI_CPPC_LIB
|
||||
void init_freq_invariance_cppc(void);
|
||||
#define init_freq_invariance_cppc init_freq_invariance_cppc
|
||||
#define arch_init_invariance_cppc init_freq_invariance_cppc
|
||||
|
||||
bool amd_set_max_freq_ratio(u64 *ratio);
|
||||
#else
|
||||
|
@ -81,6 +81,9 @@ obj-$(CONFIG_ACPI_AC) += ac.o
|
||||
obj-$(CONFIG_ACPI_BUTTON) += button.o
|
||||
obj-$(CONFIG_ACPI_TINY_POWER_BUTTON) += tiny-power-button.o
|
||||
obj-$(CONFIG_ACPI_FAN) += fan.o
|
||||
fan-objs := fan_core.o
|
||||
fan-objs += fan_attr.o
|
||||
|
||||
obj-$(CONFIG_ACPI_VIDEO) += video.o
|
||||
obj-$(CONFIG_ACPI_TAD) += acpi_tad.o
|
||||
obj-$(CONFIG_ACPI_PCI_SLOT) += pci_slot.o
|
||||
|
@ -59,6 +59,10 @@ MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
|
||||
|
||||
static const struct acpi_device_id battery_device_ids[] = {
|
||||
{"PNP0C0A", 0},
|
||||
|
||||
/* Microsoft Surface Go 3 */
|
||||
{"MSHW0146", 0},
|
||||
|
||||
{"", 0},
|
||||
};
|
||||
|
||||
@ -1148,6 +1152,14 @@ static const struct dmi_system_id bat_dmi_table[] __initconst = {
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Microsoft Surface Go 3 */
|
||||
.callback = battery_notification_delay_quirk,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"),
|
||||
},
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
|
@ -633,8 +633,8 @@ static bool is_cppc_supported(int revision, int num_ent)
|
||||
* )
|
||||
*/
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
@ -819,7 +819,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;
|
||||
|
@ -168,7 +168,7 @@ struct acpi_ec_query {
|
||||
};
|
||||
|
||||
static int acpi_ec_submit_query(struct acpi_ec *ec);
|
||||
static bool advance_transaction(struct acpi_ec *ec, bool interrupt);
|
||||
static void advance_transaction(struct acpi_ec *ec, bool interrupt);
|
||||
static void acpi_ec_event_handler(struct work_struct *work);
|
||||
|
||||
struct acpi_ec *first_ec;
|
||||
@ -441,36 +441,35 @@ static bool acpi_ec_submit_flushable_request(struct acpi_ec *ec)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool acpi_ec_submit_event(struct acpi_ec *ec)
|
||||
static void acpi_ec_submit_event(struct acpi_ec *ec)
|
||||
{
|
||||
/*
|
||||
* It is safe to mask the events here, because acpi_ec_close_event()
|
||||
* will run at least once after this.
|
||||
*/
|
||||
acpi_ec_mask_events(ec);
|
||||
if (!acpi_ec_event_enabled(ec))
|
||||
return false;
|
||||
return;
|
||||
|
||||
if (ec->event_state == EC_EVENT_READY) {
|
||||
ec_dbg_evt("Command(%s) submitted/blocked",
|
||||
acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY));
|
||||
if (ec->event_state != EC_EVENT_READY)
|
||||
return;
|
||||
|
||||
ec->event_state = EC_EVENT_IN_PROGRESS;
|
||||
/*
|
||||
* If events_to_process is greqter than 0 at this point, the
|
||||
* while () loop in acpi_ec_event_handler() is still running
|
||||
* and incrementing events_to_process will cause it to invoke
|
||||
* acpi_ec_submit_query() once more, so it is not necessary to
|
||||
* queue up the event work to start the same loop again.
|
||||
*/
|
||||
if (ec->events_to_process++ > 0)
|
||||
return true;
|
||||
|
||||
ec->events_in_progress++;
|
||||
return queue_work(ec_wq, &ec->work);
|
||||
}
|
||||
ec_dbg_evt("Command(%s) submitted/blocked",
|
||||
acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY));
|
||||
|
||||
ec->event_state = EC_EVENT_IN_PROGRESS;
|
||||
/*
|
||||
* The event handling work has not been completed yet, so it needs to be
|
||||
* flushed.
|
||||
* If events_to_process is greater than 0 at this point, the while ()
|
||||
* loop in acpi_ec_event_handler() is still running and incrementing
|
||||
* events_to_process will cause it to invoke acpi_ec_submit_query() once
|
||||
* more, so it is not necessary to queue up the event work to start the
|
||||
* same loop again.
|
||||
*/
|
||||
return true;
|
||||
if (ec->events_to_process++ > 0)
|
||||
return;
|
||||
|
||||
ec->events_in_progress++;
|
||||
queue_work(ec_wq, &ec->work);
|
||||
}
|
||||
|
||||
static void acpi_ec_complete_event(struct acpi_ec *ec)
|
||||
@ -655,11 +654,10 @@ static void acpi_ec_spurious_interrupt(struct acpi_ec *ec, struct transaction *t
|
||||
acpi_ec_mask_events(ec);
|
||||
}
|
||||
|
||||
static bool advance_transaction(struct acpi_ec *ec, bool interrupt)
|
||||
static void advance_transaction(struct acpi_ec *ec, bool interrupt)
|
||||
{
|
||||
struct transaction *t = ec->curr;
|
||||
bool wakeup = false;
|
||||
bool ret = false;
|
||||
u8 status;
|
||||
|
||||
ec_dbg_stm("%s (%d)", interrupt ? "IRQ" : "TASK", smp_processor_id());
|
||||
@ -724,12 +722,10 @@ static bool advance_transaction(struct acpi_ec *ec, bool interrupt)
|
||||
|
||||
out:
|
||||
if (status & ACPI_EC_FLAG_SCI)
|
||||
ret = acpi_ec_submit_event(ec);
|
||||
acpi_ec_submit_event(ec);
|
||||
|
||||
if (wakeup && interrupt)
|
||||
wake_up(&ec->wait);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void start_transaction(struct acpi_ec *ec)
|
||||
@ -1242,6 +1238,7 @@ static void acpi_ec_event_handler(struct work_struct *work)
|
||||
acpi_ec_submit_query(ec);
|
||||
|
||||
spin_lock_irq(&ec->lock);
|
||||
|
||||
ec->events_to_process--;
|
||||
}
|
||||
|
||||
@ -1250,27 +1247,30 @@ static void acpi_ec_event_handler(struct work_struct *work)
|
||||
* event handling work again regardless of whether or not the query
|
||||
* queued up above is processed successfully.
|
||||
*/
|
||||
if (ec_event_clearing == ACPI_EC_EVT_TIMING_EVENT)
|
||||
if (ec_event_clearing == ACPI_EC_EVT_TIMING_EVENT) {
|
||||
bool guard_timeout;
|
||||
|
||||
acpi_ec_complete_event(ec);
|
||||
else
|
||||
acpi_ec_close_event(ec);
|
||||
|
||||
spin_unlock_irq(&ec->lock);
|
||||
ec_dbg_evt("Event stopped");
|
||||
|
||||
ec_dbg_evt("Event stopped");
|
||||
spin_unlock_irq(&ec->lock);
|
||||
|
||||
guard_timeout = !!ec_guard(ec);
|
||||
|
||||
if (ec_event_clearing == ACPI_EC_EVT_TIMING_EVENT && ec_guard(ec)) {
|
||||
spin_lock_irq(&ec->lock);
|
||||
|
||||
/* Take care of SCI_EVT unless someone else is doing that. */
|
||||
if (!ec->curr)
|
||||
if (guard_timeout && !ec->curr)
|
||||
advance_transaction(ec, false);
|
||||
} else {
|
||||
acpi_ec_close_event(ec);
|
||||
|
||||
spin_unlock_irq(&ec->lock);
|
||||
ec_dbg_evt("Event stopped");
|
||||
}
|
||||
|
||||
spin_lock_irq(&ec->lock);
|
||||
ec->events_in_progress--;
|
||||
|
||||
spin_unlock_irq(&ec->lock);
|
||||
}
|
||||
|
||||
@ -2051,6 +2051,11 @@ void acpi_ec_set_gpe_wake_mask(u8 action)
|
||||
acpi_set_gpe_wake_mask(NULL, first_ec->gpe, action);
|
||||
}
|
||||
|
||||
static bool acpi_ec_work_in_progress(struct acpi_ec *ec)
|
||||
{
|
||||
return ec->events_in_progress + ec->queries_in_progress > 0;
|
||||
}
|
||||
|
||||
bool acpi_ec_dispatch_gpe(void)
|
||||
{
|
||||
bool work_in_progress = false;
|
||||
@ -2084,7 +2089,8 @@ bool acpi_ec_dispatch_gpe(void)
|
||||
if (acpi_ec_gpe_status_set(first_ec)) {
|
||||
pm_pr_dbg("ACPI EC GPE status set\n");
|
||||
|
||||
work_in_progress = advance_transaction(first_ec, false);
|
||||
advance_transaction(first_ec, false);
|
||||
work_in_progress = acpi_ec_work_in_progress(first_ec);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&first_ec->lock);
|
||||
@ -2102,8 +2108,7 @@ bool acpi_ec_dispatch_gpe(void)
|
||||
|
||||
spin_lock_irq(&first_ec->lock);
|
||||
|
||||
work_in_progress = first_ec->events_in_progress +
|
||||
first_ec->queries_in_progress > 0;
|
||||
work_in_progress = acpi_ec_work_in_progress(first_ec);
|
||||
|
||||
spin_unlock_irq(&first_ec->lock);
|
||||
} while (work_in_progress && !pm_wakeup_pending());
|
||||
|
@ -6,9 +6,53 @@
|
||||
*
|
||||
* Add new device IDs before the generic ACPI fan one.
|
||||
*/
|
||||
|
||||
#ifndef _ACPI_FAN_H_
|
||||
#define _ACPI_FAN_H_
|
||||
|
||||
#define ACPI_FAN_DEVICE_IDS \
|
||||
{"INT3404", }, /* Fan */ \
|
||||
{"INTC1044", }, /* Fan for Tiger Lake generation */ \
|
||||
{"INTC1048", }, /* Fan for Alder Lake generation */ \
|
||||
{"INTC10A2", }, /* Fan for Raptor Lake generation */ \
|
||||
{"PNP0C0B", } /* Generic ACPI fan */
|
||||
|
||||
#define ACPI_FPS_NAME_LEN 20
|
||||
|
||||
struct acpi_fan_fps {
|
||||
u64 control;
|
||||
u64 trip_point;
|
||||
u64 speed;
|
||||
u64 noise_level;
|
||||
u64 power;
|
||||
char name[ACPI_FPS_NAME_LEN];
|
||||
struct device_attribute dev_attr;
|
||||
};
|
||||
|
||||
struct acpi_fan_fif {
|
||||
u8 revision;
|
||||
u8 fine_grain_ctrl;
|
||||
u8 step_size;
|
||||
u8 low_speed_notification;
|
||||
};
|
||||
|
||||
struct acpi_fan_fst {
|
||||
u64 revision;
|
||||
u64 control;
|
||||
u64 speed;
|
||||
};
|
||||
|
||||
struct acpi_fan {
|
||||
bool acpi4;
|
||||
struct acpi_fan_fif fif;
|
||||
struct acpi_fan_fps *fps;
|
||||
int fps_count;
|
||||
struct thermal_cooling_device *cdev;
|
||||
struct device_attribute fst_speed;
|
||||
struct device_attribute fine_grain_control;
|
||||
};
|
||||
|
||||
int acpi_fan_get_fst(struct acpi_device *device, struct acpi_fan_fst *fst);
|
||||
int acpi_fan_create_attributes(struct acpi_device *device);
|
||||
void acpi_fan_delete_attributes(struct acpi_device *device);
|
||||
#endif
|
||||
|
137
drivers/acpi/fan_attr.c
Normal file
137
drivers/acpi/fan_attr.c
Normal file
@ -0,0 +1,137 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* fan_attr.c - Create extra attributes for ACPI Fan driver
|
||||
*
|
||||
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
|
||||
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
|
||||
* Copyright (C) 2022 Intel Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include "fan.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static ssize_t show_state(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct acpi_fan_fps *fps = container_of(attr, struct acpi_fan_fps, dev_attr);
|
||||
int count;
|
||||
|
||||
if (fps->control == 0xFFFFFFFF || fps->control > 100)
|
||||
count = scnprintf(buf, PAGE_SIZE, "not-defined:");
|
||||
else
|
||||
count = scnprintf(buf, PAGE_SIZE, "%lld:", fps->control);
|
||||
|
||||
if (fps->trip_point == 0xFFFFFFFF || fps->trip_point > 9)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:");
|
||||
else
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->trip_point);
|
||||
|
||||
if (fps->speed == 0xFFFFFFFF)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:");
|
||||
else
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->speed);
|
||||
|
||||
if (fps->noise_level == 0xFFFFFFFF)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:");
|
||||
else
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->noise_level * 100);
|
||||
|
||||
if (fps->power == 0xFFFFFFFF)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined\n");
|
||||
else
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld\n", fps->power);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_fan_speed(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct acpi_device *acpi_dev = container_of(dev, struct acpi_device, dev);
|
||||
struct acpi_fan_fst fst;
|
||||
int status;
|
||||
|
||||
status = acpi_fan_get_fst(acpi_dev, &fst);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
return sprintf(buf, "%lld\n", fst.speed);
|
||||
}
|
||||
|
||||
static ssize_t show_fine_grain_control(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct acpi_device *acpi_dev = container_of(dev, struct acpi_device, dev);
|
||||
struct acpi_fan *fan = acpi_driver_data(acpi_dev);
|
||||
|
||||
return sprintf(buf, "%d\n", fan->fif.fine_grain_ctrl);
|
||||
}
|
||||
|
||||
int acpi_fan_create_attributes(struct acpi_device *device)
|
||||
{
|
||||
struct acpi_fan *fan = acpi_driver_data(device);
|
||||
int i, status;
|
||||
|
||||
sysfs_attr_init(&fan->fine_grain_control.attr);
|
||||
fan->fine_grain_control.show = show_fine_grain_control;
|
||||
fan->fine_grain_control.store = NULL;
|
||||
fan->fine_grain_control.attr.name = "fine_grain_control";
|
||||
fan->fine_grain_control.attr.mode = 0444;
|
||||
status = sysfs_create_file(&device->dev.kobj, &fan->fine_grain_control.attr);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
/* _FST is present if we are here */
|
||||
sysfs_attr_init(&fan->fst_speed.attr);
|
||||
fan->fst_speed.show = show_fan_speed;
|
||||
fan->fst_speed.store = NULL;
|
||||
fan->fst_speed.attr.name = "fan_speed_rpm";
|
||||
fan->fst_speed.attr.mode = 0444;
|
||||
status = sysfs_create_file(&device->dev.kobj, &fan->fst_speed.attr);
|
||||
if (status)
|
||||
goto rem_fine_grain_attr;
|
||||
|
||||
for (i = 0; i < fan->fps_count; ++i) {
|
||||
struct acpi_fan_fps *fps = &fan->fps[i];
|
||||
|
||||
snprintf(fps->name, ACPI_FPS_NAME_LEN, "state%d", i);
|
||||
sysfs_attr_init(&fps->dev_attr.attr);
|
||||
fps->dev_attr.show = show_state;
|
||||
fps->dev_attr.store = NULL;
|
||||
fps->dev_attr.attr.name = fps->name;
|
||||
fps->dev_attr.attr.mode = 0444;
|
||||
status = sysfs_create_file(&device->dev.kobj, &fps->dev_attr.attr);
|
||||
if (status) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < i; ++j)
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fps[j].dev_attr.attr);
|
||||
goto rem_fst_attr;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
rem_fst_attr:
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fst_speed.attr);
|
||||
|
||||
rem_fine_grain_attr:
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fine_grain_control.attr);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void acpi_fan_delete_attributes(struct acpi_device *device)
|
||||
{
|
||||
struct acpi_fan *fan = acpi_driver_data(device);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < fan->fps_count; ++i)
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fps[i].dev_attr.attr);
|
||||
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fst_speed.attr);
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fine_grain_control.attr);
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* acpi_fan.c - ACPI Fan Driver ($Revision: 29 $)
|
||||
* fan_core.c - ACPI Fan core Driver
|
||||
*
|
||||
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
|
||||
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
|
||||
* Copyright (C) 2022 Intel Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
@ -45,33 +46,6 @@ static const struct dev_pm_ops acpi_fan_pm = {
|
||||
#define FAN_PM_OPS_PTR NULL
|
||||
#endif
|
||||
|
||||
#define ACPI_FPS_NAME_LEN 20
|
||||
|
||||
struct acpi_fan_fps {
|
||||
u64 control;
|
||||
u64 trip_point;
|
||||
u64 speed;
|
||||
u64 noise_level;
|
||||
u64 power;
|
||||
char name[ACPI_FPS_NAME_LEN];
|
||||
struct device_attribute dev_attr;
|
||||
};
|
||||
|
||||
struct acpi_fan_fif {
|
||||
u64 revision;
|
||||
u64 fine_grain_ctrl;
|
||||
u64 step_size;
|
||||
u64 low_speed_notification;
|
||||
};
|
||||
|
||||
struct acpi_fan {
|
||||
bool acpi4;
|
||||
struct acpi_fan_fif fif;
|
||||
struct acpi_fan_fps *fps;
|
||||
int fps_count;
|
||||
struct thermal_cooling_device *cdev;
|
||||
};
|
||||
|
||||
static struct platform_driver acpi_fan_driver = {
|
||||
.probe = acpi_fan_probe,
|
||||
.remove = acpi_fan_remove,
|
||||
@ -89,25 +63,29 @@ static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long
|
||||
struct acpi_device *device = cdev->devdata;
|
||||
struct acpi_fan *fan = acpi_driver_data(device);
|
||||
|
||||
if (fan->acpi4)
|
||||
*state = fan->fps_count - 1;
|
||||
else
|
||||
if (fan->acpi4) {
|
||||
if (fan->fif.fine_grain_ctrl)
|
||||
*state = 100 / fan->fif.step_size;
|
||||
else
|
||||
*state = fan->fps_count - 1;
|
||||
} else {
|
||||
*state = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)
|
||||
int acpi_fan_get_fst(struct acpi_device *device, struct acpi_fan_fst *fst)
|
||||
{
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
struct acpi_fan *fan = acpi_driver_data(device);
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
int control, i;
|
||||
int ret = 0;
|
||||
|
||||
status = acpi_evaluate_object(device->handle, "_FST", NULL, &buffer);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(&device->dev, "Get fan state failed\n");
|
||||
return status;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
obj = buffer.pointer;
|
||||
@ -115,35 +93,52 @@ static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)
|
||||
obj->package.count != 3 ||
|
||||
obj->package.elements[1].type != ACPI_TYPE_INTEGER) {
|
||||
dev_err(&device->dev, "Invalid _FST data\n");
|
||||
status = -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
control = obj->package.elements[1].integer.value;
|
||||
for (i = 0; i < fan->fps_count; i++) {
|
||||
/*
|
||||
* When Fine Grain Control is set, return the state
|
||||
* corresponding to maximum fan->fps[i].control
|
||||
* value compared to the current speed. Here the
|
||||
* fan->fps[] is sorted array with increasing speed.
|
||||
*/
|
||||
if (fan->fif.fine_grain_ctrl && control < fan->fps[i].control) {
|
||||
i = (i > 0) ? i - 1 : 0;
|
||||
break;
|
||||
} else if (control == fan->fps[i].control) {
|
||||
break;
|
||||
fst->revision = obj->package.elements[0].integer.value;
|
||||
fst->control = obj->package.elements[1].integer.value;
|
||||
fst->speed = obj->package.elements[2].integer.value;
|
||||
|
||||
err:
|
||||
kfree(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)
|
||||
{
|
||||
struct acpi_fan *fan = acpi_driver_data(device);
|
||||
struct acpi_fan_fst fst;
|
||||
int status, i;
|
||||
|
||||
status = acpi_fan_get_fst(device, &fst);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
if (fan->fif.fine_grain_ctrl) {
|
||||
/* This control should be same what we set using _FSL by spec */
|
||||
if (fst.control > 100) {
|
||||
dev_dbg(&device->dev, "Invalid control value returned\n");
|
||||
goto match_fps;
|
||||
}
|
||||
|
||||
*state = (int) fst.control / fan->fif.step_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
match_fps:
|
||||
for (i = 0; i < fan->fps_count; i++) {
|
||||
if (fst.control == fan->fps[i].control)
|
||||
break;
|
||||
}
|
||||
if (i == fan->fps_count) {
|
||||
dev_dbg(&device->dev, "Invalid control value returned\n");
|
||||
status = -EINVAL;
|
||||
goto err;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*state = i;
|
||||
|
||||
err:
|
||||
kfree(obj);
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -187,15 +182,30 @@ static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state)
|
||||
{
|
||||
struct acpi_fan *fan = acpi_driver_data(device);
|
||||
acpi_status status;
|
||||
u64 value = state;
|
||||
int max_state;
|
||||
|
||||
if (state >= fan->fps_count)
|
||||
if (fan->fif.fine_grain_ctrl)
|
||||
max_state = 100 / fan->fif.step_size;
|
||||
else
|
||||
max_state = fan->fps_count - 1;
|
||||
|
||||
if (state > max_state)
|
||||
return -EINVAL;
|
||||
|
||||
status = acpi_execute_simple_method(device->handle, "_FSL",
|
||||
fan->fps[state].control);
|
||||
if (fan->fif.fine_grain_ctrl) {
|
||||
value *= fan->fif.step_size;
|
||||
/* Spec allows compensate the last step only */
|
||||
if (value + fan->fif.step_size > 100)
|
||||
value = 100;
|
||||
} else {
|
||||
value = fan->fps[state].control;
|
||||
}
|
||||
|
||||
status = acpi_execute_simple_method(device->handle, "_FSL", value);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_dbg(&device->dev, "Failed to set state by _FSL\n");
|
||||
return status;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -237,7 +247,8 @@ static int acpi_fan_get_fif(struct acpi_device *device)
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
struct acpi_fan *fan = acpi_driver_data(device);
|
||||
struct acpi_buffer format = { sizeof("NNNN"), "NNNN" };
|
||||
struct acpi_buffer fif = { sizeof(fan->fif), &fan->fif };
|
||||
u64 fields[4];
|
||||
struct acpi_buffer fif = { sizeof(fields), fields };
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
|
||||
@ -258,6 +269,17 @@ static int acpi_fan_get_fif(struct acpi_device *device)
|
||||
status = -EINVAL;
|
||||
}
|
||||
|
||||
fan->fif.revision = fields[0];
|
||||
fan->fif.fine_grain_ctrl = fields[1];
|
||||
fan->fif.step_size = fields[2];
|
||||
fan->fif.low_speed_notification = fields[3];
|
||||
|
||||
/* If there is a bug in step size and set as 0, change to 1 */
|
||||
if (!fan->fif.step_size)
|
||||
fan->fif.step_size = 1;
|
||||
/* If step size > 9, change to 9 (by spec valid values 1-9) */
|
||||
else if (fan->fif.step_size > 9)
|
||||
fan->fif.step_size = 9;
|
||||
err:
|
||||
kfree(obj);
|
||||
return status;
|
||||
@ -270,39 +292,6 @@ static int acpi_fan_speed_cmp(const void *a, const void *b)
|
||||
return fps1->speed - fps2->speed;
|
||||
}
|
||||
|
||||
static ssize_t show_state(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct acpi_fan_fps *fps = container_of(attr, struct acpi_fan_fps, dev_attr);
|
||||
int count;
|
||||
|
||||
if (fps->control == 0xFFFFFFFF || fps->control > 100)
|
||||
count = scnprintf(buf, PAGE_SIZE, "not-defined:");
|
||||
else
|
||||
count = scnprintf(buf, PAGE_SIZE, "%lld:", fps->control);
|
||||
|
||||
if (fps->trip_point == 0xFFFFFFFF || fps->trip_point > 9)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:");
|
||||
else
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->trip_point);
|
||||
|
||||
if (fps->speed == 0xFFFFFFFF)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:");
|
||||
else
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->speed);
|
||||
|
||||
if (fps->noise_level == 0xFFFFFFFF)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined:");
|
||||
else
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld:", fps->noise_level * 100);
|
||||
|
||||
if (fps->power == 0xFFFFFFFF)
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "not-defined\n");
|
||||
else
|
||||
count += scnprintf(&buf[count], PAGE_SIZE - count, "%lld\n", fps->power);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int acpi_fan_get_fps(struct acpi_device *device)
|
||||
{
|
||||
struct acpi_fan *fan = acpi_driver_data(device);
|
||||
@ -347,25 +336,6 @@ static int acpi_fan_get_fps(struct acpi_device *device)
|
||||
sort(fan->fps, fan->fps_count, sizeof(*fan->fps),
|
||||
acpi_fan_speed_cmp, NULL);
|
||||
|
||||
for (i = 0; i < fan->fps_count; ++i) {
|
||||
struct acpi_fan_fps *fps = &fan->fps[i];
|
||||
|
||||
snprintf(fps->name, ACPI_FPS_NAME_LEN, "state%d", i);
|
||||
sysfs_attr_init(&fps->dev_attr.attr);
|
||||
fps->dev_attr.show = show_state;
|
||||
fps->dev_attr.store = NULL;
|
||||
fps->dev_attr.attr.name = fps->name;
|
||||
fps->dev_attr.attr.mode = 0444;
|
||||
status = sysfs_create_file(&device->dev.kobj, &fps->dev_attr.attr);
|
||||
if (status) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < i; ++j)
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fps[j].dev_attr.attr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
err:
|
||||
kfree(obj);
|
||||
return status;
|
||||
@ -396,6 +366,10 @@ static int acpi_fan_probe(struct platform_device *pdev)
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
result = acpi_fan_create_attributes(device);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
fan->acpi4 = true;
|
||||
} else {
|
||||
result = acpi_device_update_power(device, NULL);
|
||||
@ -437,12 +411,8 @@ static int acpi_fan_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
|
||||
err_end:
|
||||
if (fan->acpi4) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < fan->fps_count; ++i)
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fps[i].dev_attr.attr);
|
||||
}
|
||||
if (fan->acpi4)
|
||||
acpi_fan_delete_attributes(device);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -453,10 +423,8 @@ static int acpi_fan_remove(struct platform_device *pdev)
|
||||
|
||||
if (fan->acpi4) {
|
||||
struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < fan->fps_count; ++i)
|
||||
sysfs_remove_file(&device->dev.kobj, &fan->fps[i].dev_attr.attr);
|
||||
acpi_fan_delete_attributes(device);
|
||||
}
|
||||
sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling");
|
||||
sysfs_remove_link(&fan->cdev->device.kobj, "device");
|
@ -339,6 +339,46 @@ bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu)
|
||||
return !ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI_CPPC_LIB
|
||||
#include <acpi/cppc_acpi.h>
|
||||
|
||||
void topology_init_cpu_capacity_cppc(void)
|
||||
{
|
||||
struct cppc_perf_caps perf_caps;
|
||||
int cpu;
|
||||
|
||||
if (likely(acpi_disabled || !acpi_cpc_valid()))
|
||||
return;
|
||||
|
||||
raw_capacity = kcalloc(num_possible_cpus(), sizeof(*raw_capacity),
|
||||
GFP_KERNEL);
|
||||
if (!raw_capacity)
|
||||
return;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (!cppc_get_perf_caps(cpu, &perf_caps) &&
|
||||
(perf_caps.highest_perf >= perf_caps.nominal_perf) &&
|
||||
(perf_caps.highest_perf >= perf_caps.lowest_perf)) {
|
||||
raw_capacity[cpu] = perf_caps.highest_perf;
|
||||
pr_debug("cpu_capacity: CPU%d cpu_capacity=%u (raw).\n",
|
||||
cpu, raw_capacity[cpu]);
|
||||
continue;
|
||||
}
|
||||
|
||||
pr_err("cpu_capacity: CPU%d missing/invalid highest performance.\n", cpu);
|
||||
pr_err("cpu_capacity: partial information: fallback to 1024 for all CPUs\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
topology_normalize_cpu_scale();
|
||||
schedule_work(&update_topology_flags_work);
|
||||
pr_debug("cpu_capacity: cpu_capacity initialization done\n");
|
||||
|
||||
exit:
|
||||
free_raw_capacity();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
static cpumask_var_t cpus_to_visit;
|
||||
static void parsing_done_workfn(struct work_struct *work);
|
||||
@ -387,9 +427,8 @@ static int __init register_cpufreq_notifier(void)
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* on ACPI-based systems we need to use the default cpu capacity
|
||||
* until we have the necessary code to parse the cpu capacity, so
|
||||
* skip registering cpufreq notifier.
|
||||
* On ACPI-based systems skip registering cpufreq notifier as cpufreq
|
||||
* information is not needed for cpu capacity initialization.
|
||||
*/
|
||||
if (!acpi_disabled || !raw_capacity)
|
||||
return -EINVAL;
|
||||
|
@ -11,6 +11,10 @@
|
||||
void topology_normalize_cpu_scale(void);
|
||||
int topology_update_cpu_topology(void);
|
||||
|
||||
#ifdef CONFIG_ACPI_CPPC_LIB
|
||||
void topology_init_cpu_capacity_cppc(void);
|
||||
#endif
|
||||
|
||||
struct device_node;
|
||||
bool topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user