Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git

This commit is contained in:
Stephen Rothwell 2024-12-20 14:16:42 +11:00
commit 971c813ec9
61 changed files with 2067 additions and 887 deletions

View File

@ -33,3 +33,8 @@ Description: Reading this file gives the current selected profile for this
source such as e.g. a hotkey triggered profile change handled
either directly by the embedded-controller or fully handled
inside the kernel.
This file may also emit the string 'custom' to indicate
that multiple platform profiles drivers are in use but
have different values. This string can not be written to
this interface and is solely for informational purposes.

View File

@ -40,3 +40,41 @@ added. Drivers which wish to introduce new profile names must:
1. Explain why the existing profile names cannot be used.
2. Add the new profile name, along with a clear description of the
expected behaviour, to the sysfs-platform_profile ABI documentation.
"Custom" profile support
========================
The platform_profile class also supports profiles advertising a "custom"
profile. This is intended to be set by drivers when the setttings in the
driver have been modified in a way that a standard profile doesn't represent
the current state.
Multiple driver support
=======================
When multiple drivers on a system advertise a platform profile handler, the
platform profile handler core will only advertise the profiles that are
common between all drivers to the ``/sys/firmware/acpi`` interfaces.
This is to ensure there is no ambiguity on what the profile names mean when
all handlers don't support a profile.
Individual drivers will register a 'platform_profile' class device that has
similar semantics as the ``/sys/firmware/acpi/platform_profile`` interface.
To discover which driver is associated with a platform profile handler the
user can read the ``name`` attribute of the class device.
To discover available profiles from the class interface the user can read the
``choices`` attribute.
If a user wants to select a profile for a specific driver, they can do so
by writing to the ``profile`` attribute of the driver's class device.
This will allow users to set different profiles for different drivers on the
same system. If the selected profile by individual drivers differs the
platform profile handler core will display the profile 'custom' to indicate
that the profiles are not the same.
While the ``platform_profile`` attribute has the value ``custom``, writing a
common profile from ``platform_profile_choices`` to the platform_profile
attribute of the platform profile handler core will set the profile for all
drivers.

View File

@ -1133,7 +1133,7 @@ F: include/linux/pds/
AMD PMC DRIVER
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
S: Supported
F: drivers/platform/x86/amd/pmc/
AMD PMF DRIVER

View File

@ -50,6 +50,12 @@ enum hsmp_message_ids {
HSMP_GET_METRIC_TABLE_VER, /* 23h Get metrics table version */
HSMP_GET_METRIC_TABLE, /* 24h Get metrics table */
HSMP_GET_METRIC_TABLE_DRAM_ADDR,/* 25h Get metrics table dram address */
HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */
HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */
HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */
HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */
HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */
HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */
HSMP_MSG_ID_MAX,
};
@ -65,6 +71,7 @@ enum hsmp_msg_type {
HSMP_RSVD = -1,
HSMP_SET = 0,
HSMP_GET = 1,
HSMP_SET_GET = 2,
};
enum hsmp_proto_versions {
@ -72,7 +79,8 @@ enum hsmp_proto_versions {
HSMP_PROTO_VER3,
HSMP_PROTO_VER4,
HSMP_PROTO_VER5,
HSMP_PROTO_VER6
HSMP_PROTO_VER6,
HSMP_PROTO_VER7
};
struct hsmp_msg_desc {
@ -300,7 +308,7 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
* HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0
* input: args[0] = power efficiency mode[2:0]
*/
{1, 0, HSMP_SET},
{1, 1, HSMP_SET_GET},
/*
* HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0
@ -325,6 +333,58 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
* output: args[1] = upper 32 bits of the address
*/
{0, 2, HSMP_GET},
/*
* HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0
* input: args[0] = min xGMI p-state[15:8] + max xGMI p-state[7:0]
*/
{1, 0, HSMP_SET},
/*
* HSMP_CPU_RAIL_ISO_FREQ_POLICY, num_args = 1, response_sz = 1
* input: args[0] = set/get policy[31] +
* disable/enable independent control[0]
* output: args[0] = current policy[0]
*/
{1, 1, HSMP_SET_GET},
/*
* HSMP_DFC_ENABLE_CTRL, num_args = 1, response_sz = 1
* input: args[0] = set/get policy[31] + enable/disable DFC[0]
* output: args[0] = current policy[0]
*/
{1, 1, HSMP_SET_GET},
/* RESERVED(0x29-0x2f) */
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
{0, 0, HSMP_RSVD},
/*
* HSMP_GET_RAPL_UNITS, response_sz = 1
* output: args[0] = tu value[19:16] + esu value[12:8]
*/
{0, 1, HSMP_GET},
/*
* HSMP_GET_RAPL_CORE_COUNTER, num_args = 1, response_sz = 1
* input: args[0] = apic id[15:0]
* output: args[0] = lower 32 bits of energy
* output: args[1] = upper 32 bits of energy
*/
{1, 2, HSMP_GET},
/*
* HSMP_GET_RAPL_PACKAGE_COUNTER, num_args = 0, response_sz = 1
* output: args[0] = lower 32 bits of energy
* output: args[1] = upper 32 bits of energy
*/
{0, 2, HSMP_GET},
};
/* Metrics table (supported only with proto version 6) */

View File

@ -5,11 +5,11 @@
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/mutex.h>
#include <linux/platform_profile.h>
#include <linux/sysfs.h>
static struct platform_profile_handler *cur_profile;
static DEFINE_MUTEX(profile_lock);
static const char * const profile_names[] = {
@ -19,99 +19,365 @@ static const char * const profile_names[] = {
[PLATFORM_PROFILE_BALANCED] = "balanced",
[PLATFORM_PROFILE_BALANCED_PERFORMANCE] = "balanced-performance",
[PLATFORM_PROFILE_PERFORMANCE] = "performance",
[PLATFORM_PROFILE_CUSTOM] = "custom",
};
static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
static ssize_t platform_profile_choices_show(struct device *dev,
struct device_attribute *attr,
char *buf)
static DEFINE_IDA(platform_profile_ida);
/**
* _commmon_choices_show - Show the available profile choices
* @choices: The available profile choices
* @buf: The buffer to write to
*
* Return: The number of bytes written
*/
static ssize_t _commmon_choices_show(unsigned long *choices, char *buf)
{
int len = 0;
int err, i;
int i, len = 0;
err = mutex_lock_interruptible(&profile_lock);
if (err)
return err;
if (!cur_profile) {
mutex_unlock(&profile_lock);
return -ENODEV;
}
for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) {
for_each_set_bit(i, choices, PLATFORM_PROFILE_LAST) {
if (len == 0)
len += sysfs_emit_at(buf, len, "%s", profile_names[i]);
else
len += sysfs_emit_at(buf, len, " %s", profile_names[i]);
}
len += sysfs_emit_at(buf, len, "\n");
mutex_unlock(&profile_lock);
return len;
}
static ssize_t platform_profile_show(struct device *dev,
struct device_attribute *attr,
char *buf)
/**
* _store_class_profile - Set the profile for a class device
* @dev: The class device
* @data: The profile to set
*
* Return: 0 on success, -errno on failure
*/
static int _store_class_profile(struct device *dev, void *data)
{
enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED;
struct platform_profile_handler *handler;
int *bit = (int *)data;
lockdep_assert_held(&profile_lock);
handler = dev_get_drvdata(dev);
if (!test_bit(*bit, handler->choices))
return -EOPNOTSUPP;
return handler->profile_set(handler, *bit);
}
/**
* _notify_class_profile - Notify the class device of a profile change
* @dev: The class device
* @data: Unused
*
* Return: 0 on success, -errno on failure
*/
static int _notify_class_profile(struct device *dev, void *data)
{
struct platform_profile_handler *handler = dev_get_drvdata(dev);
lockdep_assert_held(&profile_lock);
sysfs_notify(&handler->class_dev->kobj, NULL, "profile");
kobject_uevent(&handler->class_dev->kobj, KOBJ_CHANGE);
return 0;
}
/**
* get_class_profile - Show the current profile for a class device
* @dev: The class device
* @profile: The profile to return
*
* Return: 0 on success, -errno on failure
*/
static int get_class_profile(struct device *dev,
enum platform_profile_option *profile)
{
struct platform_profile_handler *handler;
enum platform_profile_option val;
int err;
err = mutex_lock_interruptible(&profile_lock);
if (err)
lockdep_assert_held(&profile_lock);
handler = dev_get_drvdata(dev);
err = handler->profile_get(handler, &val);
if (err) {
pr_err("Failed to get profile for handler %s\n", handler->name);
return err;
if (!cur_profile) {
mutex_unlock(&profile_lock);
return -ENODEV;
}
err = cur_profile->profile_get(cur_profile, &profile);
mutex_unlock(&profile_lock);
if (err)
return err;
if (WARN_ON(val >= PLATFORM_PROFILE_LAST))
return -EINVAL;
*profile = val;
/* Check that profile is valid index */
if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names))))
return -EIO;
return 0;
}
/**
* name_show - Show the name of the profile handler
* @dev: The device
* @attr: The attribute
* @buf: The buffer to write to
*
* Return: The number of bytes written
*/
static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct platform_profile_handler *handler = dev_get_drvdata(dev);
return sysfs_emit(buf, "%s\n", handler->name);
}
static DEVICE_ATTR_RO(name);
/**
* choices_show - Show the available profile choices
* @dev: The device
* @attr: The attribute
* @buf: The buffer to write to
*
* Return: The number of bytes written
*/
static ssize_t choices_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct platform_profile_handler *handler = dev_get_drvdata(dev);
return _commmon_choices_show(handler->choices, buf);
}
static DEVICE_ATTR_RO(choices);
/**
* profile_show - Show the current profile for a class device
* @dev: The device
* @attr: The attribute
* @buf: The buffer to write to
*
* Return: The number of bytes written
*/
static ssize_t profile_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
int err;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
err = get_class_profile(dev, &profile);
if (err)
return err;
}
return sysfs_emit(buf, "%s\n", profile_names[profile]);
}
static ssize_t platform_profile_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
/**
* profile_store - Set the profile for a class device
* @dev: The device
* @attr: The attribute
* @buf: The buffer to read from
* @count: The number of bytes to read
*
* Return: The number of bytes read
*/
static ssize_t profile_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int err, i;
int index, ret;
err = mutex_lock_interruptible(&profile_lock);
index = sysfs_match_string(profile_names, buf);
if (index < 0)
return -EINVAL;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
ret = _store_class_profile(dev, &index);
if (ret)
return ret;
}
sysfs_notify(acpi_kobj, NULL, "platform_profile");
return count;
}
static DEVICE_ATTR_RW(profile);
static struct attribute *profile_attrs[] = {
&dev_attr_name.attr,
&dev_attr_choices.attr,
&dev_attr_profile.attr,
NULL
};
ATTRIBUTE_GROUPS(profile);
static const struct class platform_profile_class = {
.name = "platform-profile",
.dev_groups = profile_groups,
};
/**
* _aggregate_choices - Aggregate the available profile choices
* @dev: The device
* @data: The available profile choices
*
* Return: 0 on success, -errno on failure
*/
static int _aggregate_choices(struct device *dev, void *data)
{
struct platform_profile_handler *handler;
unsigned long *aggregate = data;
lockdep_assert_held(&profile_lock);
handler = dev_get_drvdata(dev);
if (test_bit(PLATFORM_PROFILE_LAST, aggregate))
bitmap_copy(aggregate, handler->choices, PLATFORM_PROFILE_LAST);
else
bitmap_and(aggregate, handler->choices, aggregate, PLATFORM_PROFILE_LAST);
return 0;
}
/**
* platform_profile_choices_show - Show the available profile choices for legacy sysfs interface
* @dev: The device
* @attr: The attribute
* @buf: The buffer to write to
*
* Return: The number of bytes written
*/
static ssize_t platform_profile_choices_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
unsigned long aggregate[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
int err;
set_bit(PLATFORM_PROFILE_LAST, aggregate);
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
err = class_for_each_device(&platform_profile_class, NULL,
aggregate, _aggregate_choices);
if (err)
return err;
}
/* no profile handler registered any more */
if (bitmap_empty(aggregate, PLATFORM_PROFILE_LAST))
return -EINVAL;
return _commmon_choices_show(aggregate, buf);
}
/**
* _aggregate_profiles - Aggregate the profiles for legacy sysfs interface
* @dev: The device
* @data: The profile to return
*
* Return: 0 on success, -errno on failure
*/
static int _aggregate_profiles(struct device *dev, void *data)
{
enum platform_profile_option *profile = data;
enum platform_profile_option val;
int err;
err = get_class_profile(dev, &val);
if (err)
return err;
if (!cur_profile) {
mutex_unlock(&profile_lock);
return -ENODEV;
if (*profile != PLATFORM_PROFILE_LAST && *profile != val)
*profile = PLATFORM_PROFILE_CUSTOM;
else
*profile = val;
return 0;
}
/**
* _store_and_notify - Store and notify a class from legacy sysfs interface
* @dev: The device
* @data: The profile to return
*
* Return: 0 on success, -errno on failure
*/
static int _store_and_notify(struct device *dev, void *data)
{
enum platform_profile_option *profile = data;
int err;
err = _store_class_profile(dev, profile);
if (err)
return err;
return _notify_class_profile(dev, NULL);
}
/**
* platform_profile_show - Show the current profile for legacy sysfs interface
* @dev: The device
* @attr: The attribute
* @buf: The buffer to write to
*
* Return: The number of bytes written
*/
static ssize_t platform_profile_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
int err;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
err = class_for_each_device(&platform_profile_class, NULL,
&profile, _aggregate_profiles);
if (err)
return err;
}
/* no profile handler registered any more */
if (profile == PLATFORM_PROFILE_LAST)
return -EINVAL;
return sysfs_emit(buf, "%s\n", profile_names[profile]);
}
/**
* platform_profile_store - Set the profile for legacy sysfs interface
* @dev: The device
* @attr: The attribute
* @buf: The buffer to read from
* @count: The number of bytes to read
*
* Return: The number of bytes read
*/
static ssize_t platform_profile_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
int ret;
int i;
/* Scan for a matching profile */
i = sysfs_match_string(profile_names, buf);
if (i < 0) {
mutex_unlock(&profile_lock);
if (i < 0 || i == PLATFORM_PROFILE_CUSTOM)
return -EINVAL;
set_bit(PLATFORM_PROFILE_LAST, choices);
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
ret = class_for_each_device(&platform_profile_class, NULL,
choices, _aggregate_choices);
if (ret)
return ret;
if (!test_bit(i, choices))
return -EOPNOTSUPP;
ret = class_for_each_device(&platform_profile_class, NULL, &i,
_store_and_notify);
if (ret)
return ret;
}
/* Check that platform supports this profile choice */
if (!test_bit(i, cur_profile->choices)) {
mutex_unlock(&profile_lock);
return -EOPNOTSUPP;
}
sysfs_notify(acpi_kobj, NULL, "platform_profile");
err = cur_profile->profile_set(cur_profile, i);
if (!err)
sysfs_notify(acpi_kobj, NULL, "platform_profile");
mutex_unlock(&profile_lock);
if (err)
return err;
return count;
}
@ -124,54 +390,72 @@ static struct attribute *platform_profile_attrs[] = {
NULL
};
static int profile_class_registered(struct device *dev, const void *data)
{
return 1;
}
static umode_t profile_class_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
{
if (!class_find_device(&platform_profile_class, NULL, NULL, profile_class_registered))
return 0;
return attr->mode;
}
static const struct attribute_group platform_profile_group = {
.attrs = platform_profile_attrs
.attrs = platform_profile_attrs,
.is_visible = profile_class_is_visible,
};
void platform_profile_notify(void)
void platform_profile_notify(struct platform_profile_handler *pprof)
{
if (!cur_profile)
return;
scoped_cond_guard(mutex_intr, return, &profile_lock) {
_notify_class_profile(pprof->class_dev, NULL);
}
sysfs_notify(acpi_kobj, NULL, "platform_profile");
}
EXPORT_SYMBOL_GPL(platform_profile_notify);
int platform_profile_cycle(void)
{
enum platform_profile_option profile;
enum platform_profile_option next;
enum platform_profile_option next = PLATFORM_PROFILE_LAST;
enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
int err;
err = mutex_lock_interruptible(&profile_lock);
if (err)
return err;
set_bit(PLATFORM_PROFILE_LAST, choices);
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
err = class_for_each_device(&platform_profile_class, NULL,
&profile, _aggregate_profiles);
if (err)
return err;
if (!cur_profile) {
mutex_unlock(&profile_lock);
return -ENODEV;
if (profile == PLATFORM_PROFILE_CUSTOM ||
profile == PLATFORM_PROFILE_LAST)
return -EINVAL;
err = class_for_each_device(&platform_profile_class, NULL,
choices, _aggregate_choices);
if (err)
return err;
/* never iterate into a custom if all drivers supported it */
clear_bit(PLATFORM_PROFILE_CUSTOM, choices);
next = find_next_bit_wrap(choices,
PLATFORM_PROFILE_LAST,
profile + 1);
err = class_for_each_device(&platform_profile_class, NULL, &next,
_store_and_notify);
if (err)
return err;
}
err = cur_profile->profile_get(cur_profile, &profile);
if (err) {
mutex_unlock(&profile_lock);
return err;
}
sysfs_notify(acpi_kobj, NULL, "platform_profile");
next = find_next_bit_wrap(cur_profile->choices, PLATFORM_PROFILE_LAST,
profile + 1);
if (WARN_ON(next == PLATFORM_PROFILE_LAST)) {
mutex_unlock(&profile_lock);
return -EINVAL;
}
err = cur_profile->profile_set(cur_profile, next);
mutex_unlock(&profile_lock);
if (!err)
sysfs_notify(acpi_kobj, NULL, "platform_profile");
return err;
return 0;
}
EXPORT_SYMBOL_GPL(platform_profile_cycle);
@ -179,43 +463,85 @@ int platform_profile_register(struct platform_profile_handler *pprof)
{
int err;
mutex_lock(&profile_lock);
/* We can only have one active profile */
if (cur_profile) {
mutex_unlock(&profile_lock);
return -EEXIST;
}
/* Sanity check the profile handler field are set */
/* Sanity check the profile handler */
if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) ||
!pprof->profile_set || !pprof->profile_get) {
mutex_unlock(&profile_lock);
!pprof->profile_set || !pprof->profile_get) {
pr_err("platform_profile: handler is invalid\n");
return -EINVAL;
}
err = sysfs_create_group(acpi_kobj, &platform_profile_group);
if (err) {
mutex_unlock(&profile_lock);
return err;
guard(mutex)(&profile_lock);
/* create class interface for individual handler */
pprof->minor = ida_alloc(&platform_profile_ida, GFP_KERNEL);
if (pprof->minor < 0)
return pprof->minor;
pprof->class_dev = device_create(&platform_profile_class, pprof->dev,
MKDEV(0, 0), pprof, "platform-profile-%d",
pprof->minor);
if (IS_ERR(pprof->class_dev)) {
err = PTR_ERR(pprof->class_dev);
goto cleanup_ida;
}
cur_profile = pprof;
mutex_unlock(&profile_lock);
sysfs_notify(acpi_kobj, NULL, "platform_profile");
err = sysfs_update_group(acpi_kobj, &platform_profile_group);
if (err)
goto cleanup_cur;
return 0;
cleanup_cur:
device_unregister(pprof->class_dev);
cleanup_ida:
ida_free(&platform_profile_ida, pprof->minor);
return err;
}
EXPORT_SYMBOL_GPL(platform_profile_register);
int platform_profile_remove(void)
int platform_profile_remove(struct platform_profile_handler *pprof)
{
sysfs_remove_group(acpi_kobj, &platform_profile_group);
int id;
guard(mutex)(&profile_lock);
id = pprof->minor;
device_unregister(pprof->class_dev);
ida_free(&platform_profile_ida, id);
sysfs_notify(acpi_kobj, NULL, "platform_profile");
sysfs_update_group(acpi_kobj, &platform_profile_group);
mutex_lock(&profile_lock);
cur_profile = NULL;
mutex_unlock(&profile_lock);
return 0;
}
EXPORT_SYMBOL_GPL(platform_profile_remove);
static int __init platform_profile_init(void)
{
int err;
err = class_register(&platform_profile_class);
if (err)
return err;
err = sysfs_create_group(acpi_kobj, &platform_profile_group);
if (err)
class_unregister(&platform_profile_class);
return err;
}
static void __exit platform_profile_exit(void)
{
sysfs_remove_group(acpi_kobj, &platform_profile_group);
class_unregister(&platform_profile_class);
}
module_init(platform_profile_init);
module_exit(platform_profile_exit);
MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
MODULE_DESCRIPTION("ACPI platform profile sysfs interface");
MODULE_LICENSE("GPL");

View File

@ -1162,127 +1162,6 @@ static void dmi_check_onboard_devices(const struct dmi_header *dm, void *adap)
}
}
/* NOTE: Keep this list in sync with drivers/platform/x86/dell-smo8800.c */
static const char *const acpi_smo8800_ids[] = {
"SMO8800",
"SMO8801",
"SMO8810",
"SMO8811",
"SMO8820",
"SMO8821",
"SMO8830",
"SMO8831",
};
static acpi_status check_acpi_smo88xx_device(acpi_handle obj_handle,
u32 nesting_level,
void *context,
void **return_value)
{
struct acpi_device_info *info;
acpi_status status;
char *hid;
int i;
status = acpi_get_object_info(obj_handle, &info);
if (ACPI_FAILURE(status))
return AE_OK;
if (!(info->valid & ACPI_VALID_HID))
goto smo88xx_not_found;
hid = info->hardware_id.string;
if (!hid)
goto smo88xx_not_found;
i = match_string(acpi_smo8800_ids, ARRAY_SIZE(acpi_smo8800_ids), hid);
if (i < 0)
goto smo88xx_not_found;
kfree(info);
*return_value = NULL;
return AE_CTRL_TERMINATE;
smo88xx_not_found:
kfree(info);
return AE_OK;
}
static bool is_dell_system_with_lis3lv02d(void)
{
void *err = ERR_PTR(-ENOENT);
if (!dmi_match(DMI_SYS_VENDOR, "Dell Inc."))
return false;
/*
* Check that ACPI device SMO88xx is present and is functioning.
* Function acpi_get_devices() already filters all ACPI devices
* which are not present or are not functioning.
* ACPI device SMO88xx represents our ST microelectronics lis3lv02d
* accelerometer but unfortunately ACPI does not provide any other
* information (like I2C address).
*/
acpi_get_devices(NULL, check_acpi_smo88xx_device, NULL, &err);
return !IS_ERR(err);
}
/*
* Accelerometer's I2C address is not specified in DMI nor ACPI,
* so it is needed to define mapping table based on DMI product names.
*/
static const struct {
const char *dmi_product_name;
unsigned short i2c_addr;
} dell_lis3lv02d_devices[] = {
/*
* Dell platform team told us that these Latitude devices have
* ST microelectronics accelerometer at I2C address 0x29.
*/
{ "Latitude E5250", 0x29 },
{ "Latitude E5450", 0x29 },
{ "Latitude E5550", 0x29 },
{ "Latitude E6440", 0x29 },
{ "Latitude E6440 ATG", 0x29 },
{ "Latitude E6540", 0x29 },
/*
* Additional individual entries were added after verification.
*/
{ "Latitude 5480", 0x29 },
{ "Precision 3540", 0x29 },
{ "Vostro V131", 0x1d },
{ "Vostro 5568", 0x29 },
{ "XPS 15 7590", 0x29 },
};
static void register_dell_lis3lv02d_i2c_device(struct i801_priv *priv)
{
struct i2c_board_info info;
const char *dmi_product_name;
int i;
dmi_product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
for (i = 0; i < ARRAY_SIZE(dell_lis3lv02d_devices); ++i) {
if (strcmp(dmi_product_name,
dell_lis3lv02d_devices[i].dmi_product_name) == 0)
break;
}
if (i == ARRAY_SIZE(dell_lis3lv02d_devices)) {
dev_warn(&priv->pci_dev->dev,
"Accelerometer lis3lv02d is present on SMBus but its"
" address is unknown, skipping registration\n");
return;
}
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = dell_lis3lv02d_devices[i].i2c_addr;
strscpy(info.type, "lis3lv02d", I2C_NAME_SIZE);
i2c_new_client_device(&priv->adapter, &info);
}
/* Register optional targets */
static void i801_probe_optional_targets(struct i801_priv *priv)
{
@ -1302,9 +1181,6 @@ static void i801_probe_optional_targets(struct i801_priv *priv)
if (dmi_name_in_vendors("FUJITSU"))
dmi_walk(dmi_check_onboard_devices, &priv->adapter);
if (is_dell_system_with_lis3lv02d())
register_dell_lis3lv02d_i2c_device(priv);
/* Instantiate SPD EEPROMs unless the SMBus is multiplexed */
#ifdef CONFIG_I2C_I801_MUX
if (!priv->mux_pdev)

View File

@ -939,7 +939,7 @@ MODULE_DEVICE_TABLE(acpi, mlxbf_bootctl_acpi_ids);
static ssize_t mlxbf_bootctl_bootfifo_read(struct file *filp,
struct kobject *kobj,
struct bin_attribute *bin_attr,
const struct bin_attribute *bin_attr,
char *buf, loff_t pos,
size_t count)
{
@ -971,9 +971,9 @@ static ssize_t mlxbf_bootctl_bootfifo_read(struct file *filp,
return p - buf;
}
static struct bin_attribute mlxbf_bootctl_bootfifo_sysfs_attr = {
static const struct bin_attribute mlxbf_bootctl_bootfifo_sysfs_attr = {
.attr = { .name = "bootfifo", .mode = 0400 },
.read = mlxbf_bootctl_bootfifo_read,
.read_new = mlxbf_bootctl_bootfifo_read,
};
static bool mlxbf_bootctl_guid_match(const guid_t *guid,

View File

@ -1168,7 +1168,7 @@ static int mlxbf_pmc_program_l3_counter(unsigned int blk_num, u32 cnt_num, u32 e
/* Method to handle crspace counter programming */
static int mlxbf_pmc_program_crspace_counter(unsigned int blk_num, u32 cnt_num, u32 evt)
{
void *addr;
void __iomem *addr;
u32 word;
int ret;
@ -1192,7 +1192,7 @@ static int mlxbf_pmc_program_crspace_counter(unsigned int blk_num, u32 cnt_num,
/* Method to clear crspace counter value */
static int mlxbf_pmc_clear_crspace_counter(unsigned int blk_num, u32 cnt_num)
{
void *addr;
void __iomem *addr;
addr = pmc->block[blk_num].mmio_base +
MLXBF_PMC_CRSPACE_PERFMON_VAL0(pmc->block[blk_num].counters) +
@ -1405,7 +1405,7 @@ static int mlxbf_pmc_read_l3_event(unsigned int blk_num, u32 cnt_num, u64 *resul
static int mlxbf_pmc_read_crspace_event(unsigned int blk_num, u32 cnt_num, u64 *result)
{
u32 word, evt;
void *addr;
void __iomem *addr;
int ret;
addr = pmc->block[blk_num].mmio_base +

View File

@ -210,7 +210,10 @@ static int surface_platform_profile_probe(struct ssam_device *sdev)
return -ENOMEM;
tpd->sdev = sdev;
ssam_device_set_drvdata(sdev, tpd);
tpd->handler.name = "Surface Platform Profile";
tpd->handler.dev = &sdev->dev;
tpd->handler.profile_get = ssam_platform_profile_get;
tpd->handler.profile_set = ssam_platform_profile_set;
@ -226,7 +229,10 @@ static int surface_platform_profile_probe(struct ssam_device *sdev)
static void surface_platform_profile_remove(struct ssam_device *sdev)
{
platform_profile_remove();
struct ssam_platform_profile_device *tpd;
tpd = ssam_device_get_drvdata(sdev);
platform_profile_remove(&tpd->handler);
}
static const struct ssam_device_id ssam_platform_profile_match[] = {

View File

@ -30,6 +30,7 @@
#include <linux/input/sparse-keymap.h>
#include <acpi/video.h>
#include <linux/hwmon.h>
#include <linux/units.h>
#include <linux/bitfield.h>
MODULE_AUTHOR("Carlos Corbacho");
@ -70,7 +71,10 @@ MODULE_LICENSE("GPL");
#define ACER_PREDATOR_V4_THERMAL_PROFILE_EC_OFFSET 0x54
#define ACER_PREDATOR_V4_FAN_SPEED_READ_BIT_MASK GENMASK(20, 8)
#define ACER_PREDATOR_V4_RETURN_STATUS_BIT_MASK GENMASK_ULL(7, 0)
#define ACER_PREDATOR_V4_SENSOR_INDEX_BIT_MASK GENMASK_ULL(15, 8)
#define ACER_PREDATOR_V4_SENSOR_READING_BIT_MASK GENMASK_ULL(23, 8)
#define ACER_PREDATOR_V4_SUPPORTED_SENSORS_BIT_MASK GENMASK_ULL(39, 24)
/*
* Acer ACPI method GUIDs
@ -98,9 +102,17 @@ enum acer_wmi_event_ids {
};
enum acer_wmi_predator_v4_sys_info_command {
ACER_WMID_CMD_GET_PREDATOR_V4_BAT_STATUS = 0x02,
ACER_WMID_CMD_GET_PREDATOR_V4_CPU_FAN_SPEED = 0x0201,
ACER_WMID_CMD_GET_PREDATOR_V4_GPU_FAN_SPEED = 0x0601,
ACER_WMID_CMD_GET_PREDATOR_V4_SUPPORTED_SENSORS = 0x0000,
ACER_WMID_CMD_GET_PREDATOR_V4_SENSOR_READING = 0x0001,
ACER_WMID_CMD_GET_PREDATOR_V4_BAT_STATUS = 0x0002,
};
enum acer_wmi_predator_v4_sensor_id {
ACER_WMID_SENSOR_CPU_TEMPERATURE = 0x01,
ACER_WMID_SENSOR_CPU_FAN_SPEED = 0x02,
ACER_WMID_SENSOR_EXTERNAL_TEMPERATURE_2 = 0x03,
ACER_WMID_SENSOR_GPU_FAN_SPEED = 0x06,
ACER_WMID_SENSOR_GPU_TEMPERATURE = 0x0A,
};
static const struct key_entry acer_wmi_keymap[] __initconst = {
@ -246,7 +258,7 @@ struct hotkey_function_type_aa {
#define ACER_CAP_TURBO_LED BIT(8)
#define ACER_CAP_TURBO_FAN BIT(9)
#define ACER_CAP_PLATFORM_PROFILE BIT(10)
#define ACER_CAP_FAN_SPEED_READ BIT(11)
#define ACER_CAP_HWMON BIT(11)
/*
* Interface type flags
@ -271,6 +283,7 @@ static u16 commun_func_bitmap;
static u8 commun_fn_key_number;
static bool cycle_gaming_thermal_profile = true;
static bool predator_v4;
static u64 supported_sensors;
module_param(mailled, int, 0444);
module_param(brightness, int, 0444);
@ -358,7 +371,7 @@ static void __init set_quirks(void)
if (quirks->predator_v4)
interface->capability |= ACER_CAP_PLATFORM_PROFILE |
ACER_CAP_FAN_SPEED_READ;
ACER_CAP_HWMON;
}
static int __init dmi_matched(const struct dmi_system_id *dmi)
@ -393,6 +406,13 @@ static struct quirk_entry quirk_acer_predator_ph315_53 = {
.gpu_fans = 1,
};
static struct quirk_entry quirk_acer_predator_pt14_51 = {
.turbo = 1,
.cpu_fans = 1,
.gpu_fans = 1,
.predator_v4 = 1,
};
static struct quirk_entry quirk_acer_predator_v4 = {
.predator_v4 = 1,
};
@ -600,6 +620,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
},
.driver_data = &quirk_acer_predator_v4,
},
{
.callback = dmi_matched,
.ident = "Acer Predator PT14-51",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
DMI_MATCH(DMI_PRODUCT_NAME, "Predator PT14-51"),
},
.driver_data = &quirk_acer_predator_pt14_51,
},
{
.callback = set_force_caps,
.ident = "Acer Aspire Switch 10E SW3-016",
@ -1497,6 +1526,24 @@ static acpi_status WMID_gaming_get_u64(u64 *value, u32 cap)
return status;
}
static int WMID_gaming_get_sys_info(u32 command, u64 *out)
{
acpi_status status;
u64 result;
status = WMI_gaming_execute_u64(ACER_WMID_GET_GAMING_SYS_INFO_METHODID, command, &result);
if (ACPI_FAILURE(status))
return -EIO;
/* The return status must be zero for the operation to have succeeded */
if (FIELD_GET(ACER_PREDATOR_V4_RETURN_STATUS_BIT_MASK, result))
return -EIO;
*out = result;
return 0;
}
static void WMID_gaming_set_fan_mode(u8 fan_mode)
{
/* fan_mode = 1 is used for auto, fan_mode = 2 used for turbo*/
@ -1744,26 +1791,6 @@ static int acer_gsensor_event(void)
return 0;
}
static int acer_get_fan_speed(int fan)
{
if (quirks->predator_v4) {
acpi_status status;
u64 fanspeed;
status = WMI_gaming_execute_u64(
ACER_WMID_GET_GAMING_SYS_INFO_METHODID,
fan == 0 ? ACER_WMID_CMD_GET_PREDATOR_V4_CPU_FAN_SPEED :
ACER_WMID_CMD_GET_PREDATOR_V4_GPU_FAN_SPEED,
&fanspeed);
if (ACPI_FAILURE(status))
return -EIO;
return FIELD_GET(ACER_PREDATOR_V4_FAN_SPEED_READ_BIT_MASK, fanspeed);
}
return -EOPNOTSUPP;
}
/*
* Predator series turbo button
*/
@ -1873,11 +1900,13 @@ acer_predator_v4_platform_profile_set(struct platform_profile_handler *pprof,
return 0;
}
static int acer_platform_profile_setup(void)
static int acer_platform_profile_setup(struct platform_device *device)
{
if (quirks->predator_v4) {
int err;
platform_profile_handler.name = "acer-wmi";
platform_profile_handler.dev = &device->dev;
platform_profile_handler.profile_get =
acer_predator_v4_platform_profile_get;
platform_profile_handler.profile_set =
@ -1926,12 +1955,9 @@ static int acer_thermal_profile_change(void)
return err;
/* Check power source */
status = WMI_gaming_execute_u64(
ACER_WMID_GET_GAMING_SYS_INFO_METHODID,
ACER_WMID_CMD_GET_PREDATOR_V4_BAT_STATUS, &on_AC);
if (ACPI_FAILURE(status))
return -EIO;
err = WMID_gaming_get_sys_info(ACER_WMID_CMD_GET_PREDATOR_V4_BAT_STATUS, &on_AC);
if (err < 0)
return err;
switch (current_tp) {
case ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO:
@ -1986,7 +2012,7 @@ static int acer_thermal_profile_change(void)
if (tp != ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO_WMI)
last_non_turbo_profile = tp;
platform_profile_notify();
platform_profile_notify(&platform_profile_handler);
}
return 0;
@ -2530,12 +2556,12 @@ static int acer_platform_probe(struct platform_device *device)
goto error_rfkill;
if (has_cap(ACER_CAP_PLATFORM_PROFILE)) {
err = acer_platform_profile_setup();
err = acer_platform_profile_setup(device);
if (err)
goto error_platform_profile;
}
if (has_cap(ACER_CAP_FAN_SPEED_READ)) {
if (has_cap(ACER_CAP_HWMON)) {
err = acer_wmi_hwmon_init();
if (err)
goto error_hwmon;
@ -2545,7 +2571,7 @@ static int acer_platform_probe(struct platform_device *device)
error_hwmon:
if (platform_profile_support)
platform_profile_remove();
platform_profile_remove(&platform_profile_handler);
error_platform_profile:
acer_rfkill_exit();
error_rfkill:
@ -2568,7 +2594,7 @@ static void acer_platform_remove(struct platform_device *device)
acer_rfkill_exit();
if (platform_profile_support)
platform_profile_remove();
platform_profile_remove(&platform_profile_handler);
}
#ifdef CONFIG_PM_SLEEP
@ -2655,43 +2681,86 @@ static void __init create_debugfs(void)
&interface->debug.wmid_devices);
}
static const enum acer_wmi_predator_v4_sensor_id acer_wmi_temp_channel_to_sensor_id[] = {
[0] = ACER_WMID_SENSOR_CPU_TEMPERATURE,
[1] = ACER_WMID_SENSOR_GPU_TEMPERATURE,
[2] = ACER_WMID_SENSOR_EXTERNAL_TEMPERATURE_2,
};
static const enum acer_wmi_predator_v4_sensor_id acer_wmi_fan_channel_to_sensor_id[] = {
[0] = ACER_WMID_SENSOR_CPU_FAN_SPEED,
[1] = ACER_WMID_SENSOR_GPU_FAN_SPEED,
};
static umode_t acer_wmi_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type, u32 attr,
int channel)
{
enum acer_wmi_predator_v4_sensor_id sensor_id;
const u64 *supported_sensors = data;
switch (type) {
case hwmon_temp:
sensor_id = acer_wmi_temp_channel_to_sensor_id[channel];
break;
case hwmon_fan:
if (acer_get_fan_speed(channel) >= 0)
return 0444;
sensor_id = acer_wmi_fan_channel_to_sensor_id[channel];
break;
default:
return 0;
}
if (*supported_sensors & BIT(sensor_id - 1))
return 0444;
return 0;
}
static int acer_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
u64 command = ACER_WMID_CMD_GET_PREDATOR_V4_SENSOR_READING;
u64 result;
int ret;
switch (type) {
case hwmon_fan:
ret = acer_get_fan_speed(channel);
case hwmon_temp:
command |= FIELD_PREP(ACER_PREDATOR_V4_SENSOR_INDEX_BIT_MASK,
acer_wmi_temp_channel_to_sensor_id[channel]);
ret = WMID_gaming_get_sys_info(command, &result);
if (ret < 0)
return ret;
*val = ret;
break;
result = FIELD_GET(ACER_PREDATOR_V4_SENSOR_READING_BIT_MASK, result);
*val = result * MILLIDEGREE_PER_DEGREE;
return 0;
case hwmon_fan:
command |= FIELD_PREP(ACER_PREDATOR_V4_SENSOR_INDEX_BIT_MASK,
acer_wmi_fan_channel_to_sensor_id[channel]);
ret = WMID_gaming_get_sys_info(command, &result);
if (ret < 0)
return ret;
*val = FIELD_GET(ACER_PREDATOR_V4_SENSOR_READING_BIT_MASK, result);
return 0;
default:
return -EOPNOTSUPP;
}
return 0;
}
static const struct hwmon_channel_info *const acer_wmi_hwmon_info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), NULL
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT,
HWMON_T_INPUT,
HWMON_T_INPUT
),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT,
HWMON_F_INPUT
),
NULL
};
static const struct hwmon_ops acer_wmi_hwmon_ops = {
@ -2708,9 +2777,20 @@ static int acer_wmi_hwmon_init(void)
{
struct device *dev = &acer_platform_device->dev;
struct device *hwmon;
u64 result;
int ret;
ret = WMID_gaming_get_sys_info(ACER_WMID_CMD_GET_PREDATOR_V4_SUPPORTED_SENSORS, &result);
if (ret < 0)
return ret;
/* Return early if no sensors are available */
supported_sensors = FIELD_GET(ACER_PREDATOR_V4_SUPPORTED_SENSORS_BIT_MASK, result);
if (!supported_sensors)
return 0;
hwmon = devm_hwmon_device_register_with_info(dev, "acer",
&acer_platform_driver,
&supported_sensors,
&acer_wmi_hwmon_chip_info,
NULL);

View File

@ -226,7 +226,7 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
}
static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
const struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
@ -285,19 +285,19 @@ static int init_acpi(struct device *dev)
return ret;
}
static struct bin_attribute hsmp_metric_tbl_attr = {
static const struct bin_attribute hsmp_metric_tbl_attr = {
.attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444},
.read = hsmp_metric_tbl_acpi_read,
.read_new = hsmp_metric_tbl_acpi_read,
.size = sizeof(struct hsmp_metric_table),
};
static struct bin_attribute *hsmp_attr_list[] = {
static const struct bin_attribute *hsmp_attr_list[] = {
&hsmp_metric_tbl_attr,
NULL
};
static struct attribute_group hsmp_attr_grp = {
.bin_attrs = hsmp_attr_list,
static const struct attribute_group hsmp_attr_grp = {
.bin_attrs_new = hsmp_attr_list,
.is_bin_visible = hsmp_is_sock_attr_visible,
};

View File

@ -33,7 +33,13 @@
#define HSMP_WR true
#define HSMP_RD false
#define DRIVER_VERSION "2.3"
#define DRIVER_VERSION "2.4"
/*
* When same message numbers are used for both GET and SET operation,
* bit:31 indicates whether its SET or GET operation.
*/
#define CHECK_GET_BIT BIT(31)
static struct hsmp_plat_device hsmp_pdev;
@ -167,11 +173,28 @@ static int validate_message(struct hsmp_message *msg)
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
return -ENOMSG;
/* num_args and response_sz against the HSMP spec */
if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args ||
msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
/*
* num_args passed by user should match the num_args specified in
* message description table.
*/
if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args)
return -EINVAL;
/*
* Some older HSMP SET messages are updated to add GET in the same message.
* In these messages, GET returns the current value and SET also returns
* the successfully set value. To support this GET and SET in same message
* while maintaining backward compatibility for the HSMP users,
* hsmp_msg_desc_table[] indicates only maximum allowed response_sz.
*/
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) {
if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
return -EINVAL;
} else {
/* only HSMP_SET or HSMP_GET messages go through this strict check */
if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
return -EINVAL;
}
return 0;
}
@ -239,6 +262,18 @@ int hsmp_test(u16 sock_ind, u32 value)
}
EXPORT_SYMBOL_NS_GPL(hsmp_test, "AMD_HSMP");
static bool is_get_msg(struct hsmp_message *msg)
{
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_GET)
return true;
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET &&
(msg->args[0] & CHECK_GET_BIT))
return true;
return false;
}
long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
int __user *arguser = (int __user *)arg;
@ -261,7 +296,7 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
* Device is opened in O_WRONLY mode
* Execute only set/configure commands
*/
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET)
if (is_get_msg(&msg))
return -EPERM;
break;
case FMODE_READ:
@ -269,7 +304,7 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
* Device is opened in O_RDONLY mode
* Execute only get/monitor commands
*/
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET)
if (!is_get_msg(&msg))
return -EPERM;
break;
case FMODE_READ | FMODE_WRITE:

View File

@ -59,7 +59,7 @@ static int amd_hsmp_pci_rdwr(struct hsmp_socket *sock, u32 offset,
}
static ssize_t hsmp_metric_tbl_plat_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
const struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct hsmp_socket *sock;
@ -97,13 +97,13 @@ static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
* is_bin_visible function is used to show / hide the necessary groups.
*/
#define HSMP_BIN_ATTR(index, _list) \
static struct bin_attribute attr##index = { \
static const struct bin_attribute attr##index = { \
.attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444}, \
.private = (void *)index, \
.read = hsmp_metric_tbl_plat_read, \
.read_new = hsmp_metric_tbl_plat_read, \
.size = sizeof(struct hsmp_metric_table), \
}; \
static struct bin_attribute _list[] = { \
static const struct bin_attribute _list[] = { \
&attr##index, \
NULL \
}
@ -118,8 +118,8 @@ HSMP_BIN_ATTR(6, *sock6_attr_list);
HSMP_BIN_ATTR(7, *sock7_attr_list);
#define HSMP_BIN_ATTR_GRP(index, _list, _name) \
static struct attribute_group sock##index##_attr_grp = { \
.bin_attrs = _list, \
static const struct attribute_group sock##index##_attr_grp = { \
.bin_attrs_new = _list, \
.is_bin_visible = hsmp_is_sock_attr_visible, \
.name = #_name, \
}

View File

@ -4,6 +4,6 @@
# AMD Power Management Controller Driver
#
amd-pmc-objs := pmc.o pmc-quirks.o
amd-pmc-objs := pmc.o pmc-quirks.o mp1_stb.o
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
amd-pmc-$(CONFIG_AMD_MP2_STB) += mp2_stb.o

View File

@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD MP1 Smart Trace Buffer (STB) Layer
*
* Copyright (c) 2024, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
* Sanket Goswami <Sanket.Goswami@amd.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/amd_nb.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include "pmc.h"
/* STB Spill to DRAM Parameters */
#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000
#define S2D_TELEMETRY_BYTES_MAX 0x100000U
#define S2D_RSVD_RAM_SPACE 0x100000
/* STB Registers */
#define AMD_STB_PMI_0 0x03E30600
#define AMD_PMC_STB_DUMMY_PC 0xC6000007
/* STB Spill to DRAM Message Definition */
#define STB_FORCE_FLUSH_DATA 0xCF
#define FIFO_SIZE 4096
/* STB S2D(Spill to DRAM) has different message port offset */
#define AMD_S2D_REGISTER_MESSAGE 0xA20
#define AMD_S2D_REGISTER_RESPONSE 0xA80
#define AMD_S2D_REGISTER_ARGUMENT 0xA88
/* STB S2D (Spill to DRAM) message port offset for 44h model */
#define AMD_GNR_REGISTER_MESSAGE 0x524
#define AMD_GNR_REGISTER_RESPONSE 0x570
#define AMD_GNR_REGISTER_ARGUMENT 0xA40
static bool enable_stb;
module_param(enable_stb, bool, 0644);
MODULE_PARM_DESC(enable_stb, "Enable the STB debug mechanism");
static bool dump_custom_stb;
module_param(dump_custom_stb, bool, 0644);
MODULE_PARM_DESC(dump_custom_stb, "Enable to dump full STB buffer");
enum s2d_arg {
S2D_TELEMETRY_SIZE = 0x01,
S2D_PHYS_ADDR_LOW,
S2D_PHYS_ADDR_HIGH,
S2D_NUM_SAMPLES,
S2D_DRAM_SIZE,
};
struct amd_stb_v2_data {
size_t size;
u8 data[] __counted_by(size);
};
int amd_stb_write(struct amd_pmc_dev *dev, u32 data)
{
int err;
err = amd_smn_write(0, AMD_STB_PMI_0, data);
if (err) {
dev_err(dev->dev, "failed to write data in stb: 0x%X\n", AMD_STB_PMI_0);
return pcibios_err_to_errno(err);
}
return 0;
}
int amd_stb_read(struct amd_pmc_dev *dev, u32 *buf)
{
int i, err;
for (i = 0; i < FIFO_SIZE; i++) {
err = amd_smn_read(0, AMD_STB_PMI_0, buf++);
if (err) {
dev_err(dev->dev, "error reading data from stb: 0x%X\n", AMD_STB_PMI_0);
return pcibios_err_to_errno(err);
}
}
return 0;
}
static int amd_stb_debugfs_open(struct inode *inode, struct file *filp)
{
struct amd_pmc_dev *dev = filp->f_inode->i_private;
u32 size = FIFO_SIZE * sizeof(u32);
u32 *buf;
int rc;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
rc = amd_stb_read(dev, buf);
if (rc) {
kfree(buf);
return rc;
}
filp->private_data = buf;
return rc;
}
static ssize_t amd_stb_debugfs_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
if (!filp->private_data)
return -EINVAL;
return simple_read_from_buffer(buf, size, pos, filp->private_data,
FIFO_SIZE * sizeof(u32));
}
static int amd_stb_debugfs_release(struct inode *inode, struct file *filp)
{
kfree(filp->private_data);
return 0;
}
static const struct file_operations amd_stb_debugfs_fops = {
.owner = THIS_MODULE,
.open = amd_stb_debugfs_open,
.read = amd_stb_debugfs_read,
.release = amd_stb_debugfs_release,
};
/* Enhanced STB Firmware Reporting Mechanism */
static int amd_stb_handle_efr(struct file *filp)
{
struct amd_pmc_dev *dev = filp->f_inode->i_private;
struct amd_stb_v2_data *stb_data_arr;
u32 fsize;
fsize = dev->dram_size - S2D_RSVD_RAM_SPACE;
stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
if (!stb_data_arr)
return -ENOMEM;
stb_data_arr->size = fsize;
memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize);
filp->private_data = stb_data_arr;
return 0;
}
static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
{
struct amd_pmc_dev *dev = filp->f_inode->i_private;
u32 fsize, num_samples, val, stb_rdptr_offset = 0;
struct amd_stb_v2_data *stb_data_arr;
int ret;
/* Write dummy postcode while reading the STB buffer */
ret = amd_stb_write(dev, AMD_PMC_STB_DUMMY_PC);
if (ret)
dev_err(dev->dev, "error writing to STB: %d\n", ret);
/* Spill to DRAM num_samples uses separate SMU message port */
dev->msg_port = MSG_PORT_S2D;
ret = amd_pmc_send_cmd(dev, 0, &val, STB_FORCE_FLUSH_DATA, 1);
if (ret)
dev_dbg_once(dev->dev, "S2D force flush not supported: %d\n", ret);
/*
* We have a custom stb size and the PMFW is supposed to give
* the enhanced dram size. Note that we land here only for the
* platforms that support enhanced dram size reporting.
*/
if (dump_custom_stb)
return amd_stb_handle_efr(filp);
/* Get the num_samples to calculate the last push location */
ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->stb_arg.s2d_msg_id, true);
/* Clear msg_port for other SMU operation */
dev->msg_port = MSG_PORT_PMC;
if (ret) {
dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret);
return ret;
}
fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX);
stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
if (!stb_data_arr)
return -ENOMEM;
stb_data_arr->size = fsize;
/*
* Start capturing data from the last push location.
* This is for general cases, where the stb limits
* are meant for standard usage.
*/
if (num_samples > S2D_TELEMETRY_BYTES_MAX) {
/* First read oldest data starting 1 behind last write till end of ringbuffer */
stb_rdptr_offset = num_samples % S2D_TELEMETRY_BYTES_MAX;
fsize = S2D_TELEMETRY_BYTES_MAX - stb_rdptr_offset;
memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr + stb_rdptr_offset, fsize);
/* Second copy the newer samples from offset 0 - last write */
memcpy_fromio(stb_data_arr->data + fsize, dev->stb_virt_addr, stb_rdptr_offset);
} else {
memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize);
}
filp->private_data = stb_data_arr;
return 0;
}
static ssize_t amd_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size,
loff_t *pos)
{
struct amd_stb_v2_data *data = filp->private_data;
return simple_read_from_buffer(buf, size, pos, data->data, data->size);
}
static int amd_stb_debugfs_release_v2(struct inode *inode, struct file *filp)
{
kfree(filp->private_data);
return 0;
}
static const struct file_operations amd_stb_debugfs_fops_v2 = {
.owner = THIS_MODULE,
.open = amd_stb_debugfs_open_v2,
.read = amd_stb_debugfs_read_v2,
.release = amd_stb_debugfs_release_v2,
};
static void amd_stb_update_args(struct amd_pmc_dev *dev)
{
if (cpu_feature_enabled(X86_FEATURE_ZEN5))
switch (boot_cpu_data.x86_model) {
case 0x44:
dev->stb_arg.msg = AMD_GNR_REGISTER_MESSAGE;
dev->stb_arg.arg = AMD_GNR_REGISTER_ARGUMENT;
dev->stb_arg.resp = AMD_GNR_REGISTER_RESPONSE;
return;
default:
break;
}
dev->stb_arg.msg = AMD_S2D_REGISTER_MESSAGE;
dev->stb_arg.arg = AMD_S2D_REGISTER_ARGUMENT;
dev->stb_arg.resp = AMD_S2D_REGISTER_RESPONSE;
}
static bool amd_is_stb_supported(struct amd_pmc_dev *dev)
{
switch (dev->cpu_id) {
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
if (boot_cpu_data.x86_model == 0x44)
dev->stb_arg.s2d_msg_id = 0x9B;
else
dev->stb_arg.s2d_msg_id = 0xBE;
break;
case AMD_CPU_ID_PS:
dev->stb_arg.s2d_msg_id = 0x85;
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
if (boot_cpu_data.x86_model == 0x70)
dev->stb_arg.s2d_msg_id = 0xF1;
else
dev->stb_arg.s2d_msg_id = 0xDE;
break;
default:
return false;
}
amd_stb_update_args(dev);
return true;
}
int amd_stb_s2d_init(struct amd_pmc_dev *dev)
{
u32 phys_addr_low, phys_addr_hi;
u64 stb_phys_addr;
u32 size = 0;
int ret;
if (!enable_stb)
return 0;
if (amd_is_stb_supported(dev)) {
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_stb_debugfs_fops_v2);
} else {
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_stb_debugfs_fops);
return 0;
}
/* Spill to DRAM feature uses separate SMU message port */
dev->msg_port = MSG_PORT_S2D;
amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true);
if (size != S2D_TELEMETRY_BYTES_MAX)
return -EIO;
/* Get DRAM size */
ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true);
if (ret || !dev->dram_size)
dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX;
/* Get STB DRAM address */
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->stb_arg.s2d_msg_id, true);
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->stb_arg.s2d_msg_id, true);
stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
/* Clear msg_port for other SMU operation */
dev->msg_port = MSG_PORT_PMC;
dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size);
if (!dev->stb_virt_addr)
return -ENOMEM;
return 0;
}

View File

@ -12,6 +12,7 @@
#include <asm/amd_nb.h>
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/debugfs.h>
@ -40,24 +41,9 @@
#define AMD_PMC_SCRATCH_REG_1AH 0xF14
/* STB Registers */
#define AMD_PMC_STB_PMI_0 0x03E30600
#define AMD_PMC_STB_S2IDLE_PREPARE 0xC6000001
#define AMD_PMC_STB_S2IDLE_RESTORE 0xC6000002
#define AMD_PMC_STB_S2IDLE_CHECK 0xC6000003
#define AMD_PMC_STB_DUMMY_PC 0xC6000007
/* STB S2D(Spill to DRAM) has different message port offset */
#define AMD_S2D_REGISTER_MESSAGE 0xA20
#define AMD_S2D_REGISTER_RESPONSE 0xA80
#define AMD_S2D_REGISTER_ARGUMENT 0xA88
/* STB Spill to DRAM Parameters */
#define S2D_TELEMETRY_BYTES_MAX 0x100000U
#define S2D_RSVD_RAM_SPACE 0x100000
#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000
/* STB Spill to DRAM Message Definition */
#define STB_FORCE_FLUSH_DATA 0xCF
/* Base address of SMU for mapping physical address to virtual address */
#define AMD_PMC_MAPPING_SIZE 0x01000
@ -97,7 +83,6 @@
#define DELAY_MIN_US 2000
#define DELAY_MAX_US 3000
#define FIFO_SIZE 4096
enum amd_pmc_def {
MSG_TEST = 0x01,
@ -105,24 +90,39 @@ enum amd_pmc_def {
MSG_OS_HINT_RN,
};
enum s2d_arg {
S2D_TELEMETRY_SIZE = 0x01,
S2D_PHYS_ADDR_LOW,
S2D_PHYS_ADDR_HIGH,
S2D_NUM_SAMPLES,
S2D_DRAM_SIZE,
};
struct amd_pmc_stb_v2_data {
size_t size;
u8 data[] __counted_by(size);
};
struct amd_pmc_bit_map {
const char *name;
u32 bit_mask;
};
static const struct amd_pmc_bit_map soc15_ip_blk_v2[] = {
{"DISPLAY", BIT(0)},
{"CPU", BIT(1)},
{"GFX", BIT(2)},
{"VDD", BIT(3)},
{"VDD_CCX", BIT(4)},
{"ACP", BIT(5)},
{"VCN_0", BIT(6)},
{"VCN_1", BIT(7)},
{"ISP", BIT(8)},
{"NBIO", BIT(9)},
{"DF", BIT(10)},
{"USB3_0", BIT(11)},
{"USB3_1", BIT(12)},
{"LAPIC", BIT(13)},
{"USB3_2", BIT(14)},
{"USB4_RT0", BIT(15)},
{"USB4_RT1", BIT(16)},
{"USB4_0", BIT(17)},
{"USB4_1", BIT(18)},
{"MPM", BIT(19)},
{"JPEG_0", BIT(20)},
{"JPEG_1", BIT(21)},
{"IPU", BIT(22)},
{"UMSCH", BIT(23)},
{"VPE", BIT(24)},
};
static const struct amd_pmc_bit_map soc15_ip_blk[] = {
{"DISPLAY", BIT(0)},
{"CPU", BIT(1)},
@ -146,25 +146,13 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = {
{"IPU", BIT(19)},
{"UMSCH", BIT(20)},
{"VPE", BIT(21)},
{}
};
static bool enable_stb;
module_param(enable_stb, bool, 0644);
MODULE_PARM_DESC(enable_stb, "Enable the STB debug mechanism");
static bool disable_workarounds;
module_param(disable_workarounds, bool, 0644);
MODULE_PARM_DESC(disable_workarounds, "Disable workarounds for platform bugs");
static bool dump_custom_stb;
module_param(dump_custom_stb, bool, 0644);
MODULE_PARM_DESC(dump_custom_stb, "Enable to dump full STB buffer");
static struct amd_pmc_dev pmc;
static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret);
static int amd_pmc_read_stb(struct amd_pmc_dev *dev, u32 *buf);
static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data);
static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset)
{
@ -193,155 +181,6 @@ struct smu_metrics {
u64 timecondition_notmet_totaltime[32];
} __packed;
static int amd_pmc_stb_debugfs_open(struct inode *inode, struct file *filp)
{
struct amd_pmc_dev *dev = filp->f_inode->i_private;
u32 size = FIFO_SIZE * sizeof(u32);
u32 *buf;
int rc;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
rc = amd_pmc_read_stb(dev, buf);
if (rc) {
kfree(buf);
return rc;
}
filp->private_data = buf;
return rc;
}
static ssize_t amd_pmc_stb_debugfs_read(struct file *filp, char __user *buf, size_t size,
loff_t *pos)
{
if (!filp->private_data)
return -EINVAL;
return simple_read_from_buffer(buf, size, pos, filp->private_data,
FIFO_SIZE * sizeof(u32));
}
static int amd_pmc_stb_debugfs_release(struct inode *inode, struct file *filp)
{
kfree(filp->private_data);
return 0;
}
static const struct file_operations amd_pmc_stb_debugfs_fops = {
.owner = THIS_MODULE,
.open = amd_pmc_stb_debugfs_open,
.read = amd_pmc_stb_debugfs_read,
.release = amd_pmc_stb_debugfs_release,
};
/* Enhanced STB Firmware Reporting Mechanism */
static int amd_pmc_stb_handle_efr(struct file *filp)
{
struct amd_pmc_dev *dev = filp->f_inode->i_private;
struct amd_pmc_stb_v2_data *stb_data_arr;
u32 fsize;
fsize = dev->dram_size - S2D_RSVD_RAM_SPACE;
stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
if (!stb_data_arr)
return -ENOMEM;
stb_data_arr->size = fsize;
memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize);
filp->private_data = stb_data_arr;
return 0;
}
static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
{
struct amd_pmc_dev *dev = filp->f_inode->i_private;
u32 fsize, num_samples, val, stb_rdptr_offset = 0;
struct amd_pmc_stb_v2_data *stb_data_arr;
int ret;
/* Write dummy postcode while reading the STB buffer */
ret = amd_pmc_write_stb(dev, AMD_PMC_STB_DUMMY_PC);
if (ret)
dev_err(dev->dev, "error writing to STB: %d\n", ret);
/* Spill to DRAM num_samples uses separate SMU message port */
dev->msg_port = 1;
ret = amd_pmc_send_cmd(dev, 0, &val, STB_FORCE_FLUSH_DATA, 1);
if (ret)
dev_dbg_once(dev->dev, "S2D force flush not supported: %d\n", ret);
/*
* We have a custom stb size and the PMFW is supposed to give
* the enhanced dram size. Note that we land here only for the
* platforms that support enhanced dram size reporting.
*/
if (dump_custom_stb)
return amd_pmc_stb_handle_efr(filp);
/* Get the num_samples to calculate the last push location */
ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->s2d_msg_id, true);
/* Clear msg_port for other SMU operation */
dev->msg_port = 0;
if (ret) {
dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret);
return ret;
}
fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX);
stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
if (!stb_data_arr)
return -ENOMEM;
stb_data_arr->size = fsize;
/*
* Start capturing data from the last push location.
* This is for general cases, where the stb limits
* are meant for standard usage.
*/
if (num_samples > S2D_TELEMETRY_BYTES_MAX) {
/* First read oldest data starting 1 behind last write till end of ringbuffer */
stb_rdptr_offset = num_samples % S2D_TELEMETRY_BYTES_MAX;
fsize = S2D_TELEMETRY_BYTES_MAX - stb_rdptr_offset;
memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr + stb_rdptr_offset, fsize);
/* Second copy the newer samples from offset 0 - last write */
memcpy_fromio(stb_data_arr->data + fsize, dev->stb_virt_addr, stb_rdptr_offset);
} else {
memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize);
}
filp->private_data = stb_data_arr;
return 0;
}
static ssize_t amd_pmc_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size,
loff_t *pos)
{
struct amd_pmc_stb_v2_data *data = filp->private_data;
return simple_read_from_buffer(buf, size, pos, data->data, data->size);
}
static int amd_pmc_stb_debugfs_release_v2(struct inode *inode, struct file *filp)
{
kfree(filp->private_data);
return 0;
}
static const struct file_operations amd_pmc_stb_debugfs_fops_v2 = {
.owner = THIS_MODULE,
.open = amd_pmc_stb_debugfs_open_v2,
.read = amd_pmc_stb_debugfs_read_v2,
.release = amd_pmc_stb_debugfs_release_v2,
};
static void amd_pmc_get_ip_info(struct amd_pmc_dev *dev)
{
switch (dev->cpu_id) {
@ -350,18 +189,23 @@ static void amd_pmc_get_ip_info(struct amd_pmc_dev *dev)
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
dev->num_ips = 12;
dev->s2d_msg_id = 0xBE;
dev->ips_ptr = soc15_ip_blk;
dev->smu_msg = 0x538;
break;
case AMD_CPU_ID_PS:
dev->num_ips = 21;
dev->s2d_msg_id = 0x85;
dev->ips_ptr = soc15_ip_blk;
dev->smu_msg = 0x538;
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
dev->num_ips = 22;
dev->s2d_msg_id = 0xDE;
if (boot_cpu_data.x86_model == 0x70) {
dev->num_ips = ARRAY_SIZE(soc15_ip_blk_v2);
dev->ips_ptr = soc15_ip_blk_v2;
} else {
dev->num_ips = ARRAY_SIZE(soc15_ip_blk);
dev->ips_ptr = soc15_ip_blk;
}
dev->smu_msg = 0x938;
break;
}
@ -529,8 +373,8 @@ static int smu_fw_info_show(struct seq_file *s, void *unused)
seq_puts(s, "\n=== Active time (in us) ===\n");
for (idx = 0 ; idx < dev->num_ips ; idx++) {
if (soc15_ip_blk[idx].bit_mask & dev->active_ips)
seq_printf(s, "%-8s : %lld\n", soc15_ip_blk[idx].name,
if (dev->ips_ptr[idx].bit_mask & dev->active_ips)
seq_printf(s, "%-8s : %lld\n", dev->ips_ptr[idx].name,
table.timecondition_notmet_lastcapture[idx]);
}
@ -625,20 +469,6 @@ static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev)
debugfs_remove_recursive(dev->dbgfs_dir);
}
static bool amd_pmc_is_stb_supported(struct amd_pmc_dev *dev)
{
switch (dev->cpu_id) {
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
case AMD_CPU_ID_PS:
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
return true;
default:
return false;
}
}
static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
{
dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL);
@ -648,14 +478,17 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
&s0ix_stats_fops);
debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev,
&amd_pmc_idlemask_fops);
/* Enable STB only when the module_param is set */
if (enable_stb) {
if (amd_pmc_is_stb_supported(dev))
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_pmc_stb_debugfs_fops_v2);
else
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_pmc_stb_debugfs_fops);
}
static char *amd_pmc_get_msg_port(struct amd_pmc_dev *dev)
{
switch (dev->msg_port) {
case MSG_PORT_PMC:
return "PMC";
case MSG_PORT_S2D:
return "S2D";
default:
return "Invalid message port";
}
}
@ -663,10 +496,10 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev)
{
u32 value, message, argument, response;
if (dev->msg_port) {
message = AMD_S2D_REGISTER_MESSAGE;
argument = AMD_S2D_REGISTER_ARGUMENT;
response = AMD_S2D_REGISTER_RESPONSE;
if (dev->msg_port == MSG_PORT_S2D) {
message = dev->stb_arg.msg;
argument = dev->stb_arg.arg;
response = dev->stb_arg.resp;
} else {
message = dev->smu_msg;
argument = AMD_PMC_REGISTER_ARGUMENT;
@ -674,26 +507,26 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev)
}
value = amd_pmc_reg_read(dev, response);
dev_dbg(dev->dev, "AMD_%s_REGISTER_RESPONSE:%x\n", dev->msg_port ? "S2D" : "PMC", value);
dev_dbg(dev->dev, "AMD_%s_REGISTER_RESPONSE:%x\n", amd_pmc_get_msg_port(dev), value);
value = amd_pmc_reg_read(dev, argument);
dev_dbg(dev->dev, "AMD_%s_REGISTER_ARGUMENT:%x\n", dev->msg_port ? "S2D" : "PMC", value);
dev_dbg(dev->dev, "AMD_%s_REGISTER_ARGUMENT:%x\n", amd_pmc_get_msg_port(dev), value);
value = amd_pmc_reg_read(dev, message);
dev_dbg(dev->dev, "AMD_%s_REGISTER_MESSAGE:%x\n", dev->msg_port ? "S2D" : "PMC", value);
dev_dbg(dev->dev, "AMD_%s_REGISTER_MESSAGE:%x\n", amd_pmc_get_msg_port(dev), value);
}
static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret)
int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret)
{
int rc;
u32 val, message, argument, response;
mutex_lock(&dev->lock);
if (dev->msg_port) {
message = AMD_S2D_REGISTER_MESSAGE;
argument = AMD_S2D_REGISTER_ARGUMENT;
response = AMD_S2D_REGISTER_RESPONSE;
if (dev->msg_port == MSG_PORT_S2D) {
message = dev->stb_arg.msg;
argument = dev->stb_arg.arg;
response = dev->stb_arg.resp;
} else {
message = dev->smu_msg;
argument = AMD_PMC_REGISTER_ARGUMENT;
@ -881,7 +714,7 @@ static void amd_pmc_s2idle_prepare(void)
return;
}
rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_S2IDLE_PREPARE);
rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_PREPARE);
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
}
@ -900,7 +733,7 @@ static void amd_pmc_s2idle_check(void)
/* Dump the IdleMask before we add to the STB */
amd_pmc_idlemask_read(pdev, pdev->dev, NULL);
rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_S2IDLE_CHECK);
rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_CHECK);
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
}
@ -927,7 +760,7 @@ static void amd_pmc_s2idle_restore(void)
/* Let SMU know that we are looking for stats */
amd_pmc_dump_data(pdev);
rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_S2IDLE_RESTORE);
rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_RESTORE);
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
@ -975,74 +808,6 @@ static const struct pci_device_id pmc_pci_ids[] = {
{ }
};
static int amd_pmc_s2d_init(struct amd_pmc_dev *dev)
{
u32 phys_addr_low, phys_addr_hi;
u64 stb_phys_addr;
u32 size = 0;
int ret;
/* Spill to DRAM feature uses separate SMU message port */
dev->msg_port = 1;
amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->s2d_msg_id, true);
if (size != S2D_TELEMETRY_BYTES_MAX)
return -EIO;
/* Get DRAM size */
ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->s2d_msg_id, true);
if (ret || !dev->dram_size)
dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX;
/* Get STB DRAM address */
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->s2d_msg_id, true);
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->s2d_msg_id, true);
if (!phys_addr_hi && !phys_addr_low) {
dev_err(dev->dev, "STB is not enabled on the system; disable enable_stb or contact system vendor\n");
return -EINVAL;
}
stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
/* Clear msg_port for other SMU operation */
dev->msg_port = 0;
dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size);
if (!dev->stb_virt_addr)
return -ENOMEM;
return 0;
}
static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data)
{
int err;
err = amd_smn_write(0, AMD_PMC_STB_PMI_0, data);
if (err) {
dev_err(dev->dev, "failed to write data in stb: 0x%X\n", AMD_PMC_STB_PMI_0);
return pcibios_err_to_errno(err);
}
return 0;
}
static int amd_pmc_read_stb(struct amd_pmc_dev *dev, u32 *buf)
{
int i, err;
for (i = 0; i < FIFO_SIZE; i++) {
err = amd_smn_read(0, AMD_PMC_STB_PMI_0, buf++);
if (err) {
dev_err(dev->dev, "error reading data from stb: 0x%X\n", AMD_PMC_STB_PMI_0);
return pcibios_err_to_errno(err);
}
}
return 0;
}
static int amd_pmc_probe(struct platform_device *pdev)
{
struct amd_pmc_dev *dev = &pmc;
@ -1100,12 +865,6 @@ static int amd_pmc_probe(struct platform_device *pdev)
/* Get num of IP blocks within the SoC */
amd_pmc_get_ip_info(dev);
if (enable_stb && amd_pmc_is_stb_supported(dev)) {
err = amd_pmc_s2d_init(dev);
if (err)
goto err_pci_dev_put;
}
platform_set_drvdata(pdev, dev);
if (IS_ENABLED(CONFIG_SUSPEND)) {
err = acpi_register_lps0_dev(&amd_pmc_s2idle_dev_ops);
@ -1116,6 +875,10 @@ static int amd_pmc_probe(struct platform_device *pdev)
}
amd_pmc_dbgfs_register(dev);
err = amd_stb_s2d_init(dev);
if (err)
goto err_pci_dev_put;
if (IS_ENABLED(CONFIG_AMD_MP2_STB))
amd_mp2_stb_init(dev);
pm_report_max_hw_sleep(U64_MAX);

View File

@ -14,6 +14,11 @@
#include <linux/types.h>
#include <linux/mutex.h>
enum s2d_msg_port {
MSG_PORT_PMC,
MSG_PORT_S2D,
};
struct amd_mp2_dev {
void __iomem *mmio;
void __iomem *vslbase;
@ -25,24 +30,31 @@ struct amd_mp2_dev {
bool is_stb_data;
};
struct stb_arg {
u32 s2d_msg_id;
u32 msg;
u32 arg;
u32 resp;
};
struct amd_pmc_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
void __iomem *stb_virt_addr;
void __iomem *fch_virt_addr;
bool msg_port;
u32 base_addr;
u32 cpu_id;
u32 active_ips;
u32 dram_size;
u32 active_ips;
const struct amd_pmc_bit_map *ips_ptr;
u32 num_ips;
u32 s2d_msg_id;
u32 smu_msg;
/* SMU version information */
u8 smu_program;
u8 major;
u8 minor;
u8 rev;
u8 msg_port;
struct device *dev;
struct pci_dev *rdev;
struct mutex lock; /* generic mutex lock */
@ -50,6 +62,7 @@ struct amd_pmc_dev {
struct quirk_entry *quirks;
bool disable_8042_wakeup;
struct amd_mp2_dev *mp2;
struct stb_arg stb_arg;
};
void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev);
@ -70,4 +83,9 @@ void amd_mp2_stb_deinit(struct amd_pmc_dev *dev);
#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c
int amd_stb_s2d_init(struct amd_pmc_dev *dev);
int amd_stb_read(struct amd_pmc_dev *dev, u32 *buf);
int amd_stb_write(struct amd_pmc_dev *dev, u32 data);
int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret);
#endif /* PMC_H */

View File

@ -7,4 +7,4 @@
obj-$(CONFIG_AMD_PMF) += amd-pmf.o
amd-pmf-objs := core.o acpi.o sps.o \
auto-mode.o cnqf.o \
tee-if.o spc.o pmf-quirks.o
tee-if.o spc.o

View File

@ -321,6 +321,18 @@ int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req
req, sizeof(*req));
}
static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
{
struct amd_pmf_dev *pmf_dev = data;
int ret;
guard(mutex)(&pmf_dev->cb_mutex);
ret = apmf_get_sbios_requests_v2(pmf_dev, &pmf_dev->req);
if (ret)
dev_err(pmf_dev->dev, "Failed to get v2 SBIOS requests: %d\n", ret);
}
static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
{
struct amd_pmf_dev *pmf_dev = data;
@ -430,6 +442,15 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
apmf_event_handler(ahandle, 0, pmf_dev);
}
if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2) {
status = acpi_install_notify_handler(ahandle, ACPI_ALL_NOTIFY,
apmf_event_handler_v2, pmf_dev);
if (ACPI_FAILURE(status)) {
dev_err(pmf_dev->dev, "failed to install notify handler for custom BIOS inputs\n");
return -ENODEV;
}
}
return 0;
}
@ -480,6 +501,9 @@ void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev)
if (is_apmf_func_supported(pmf_dev, APMF_FUNC_AUTO_MODE) &&
is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS))
acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler);
if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2)
acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler_v2);
}
int apmf_acpi_init(struct amd_pmf_dev *pmf_dev)

View File

@ -456,7 +456,6 @@ static int amd_pmf_probe(struct platform_device *pdev)
mutex_init(&dev->lock);
mutex_init(&dev->update_mutex);
amd_pmf_quirks_init(dev);
apmf_acpi_init(dev);
platform_set_drvdata(pdev, dev);
amd_pmf_dbgfs_register(dev);

View File

@ -1,66 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Platform Management Framework Driver Quirks
*
* Copyright (c) 2024, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Mario Limonciello <mario.limonciello@amd.com>
*/
#include <linux/dmi.h>
#include "pmf.h"
struct quirk_entry {
u32 supported_func;
};
static struct quirk_entry quirk_no_sps_bug = {
.supported_func = 0x4003,
};
static const struct dmi_system_id fwbug_list[] = {
{
.ident = "ROG Zephyrus G14",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "GA403U"),
},
.driver_data = &quirk_no_sps_bug,
},
{
.ident = "ROG Ally X",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "RC72LA"),
},
.driver_data = &quirk_no_sps_bug,
},
{
.ident = "ASUS TUF Gaming A14",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "FA401W"),
},
.driver_data = &quirk_no_sps_bug,
},
{}
};
void amd_pmf_quirks_init(struct amd_pmf_dev *dev)
{
const struct dmi_system_id *dmi_id;
struct quirk_entry *quirks;
dmi_id = dmi_first_match(fwbug_list);
if (!dmi_id)
return;
quirks = dmi_id->driver_data;
if (quirks->supported_func) {
dev->supported_func = quirks->supported_func;
pr_info("Using supported funcs quirk to avoid %s platform firmware bug\n",
dmi_id->ident);
}
}

View File

@ -370,6 +370,8 @@ struct amd_pmf_dev {
struct input_dev *pmf_idev;
size_t mtable_size;
struct resource *res;
struct apmf_sbios_req_v2 req; /* To get custom bios pending request */
struct mutex cb_mutex;
};
struct apmf_sps_prop_granular_v2 {
@ -616,6 +618,16 @@ enum ta_slider {
TA_MAX,
};
enum apmf_smartpc_custom_bios_inputs {
APMF_SMARTPC_CUSTOM_BIOS_INPUT1,
APMF_SMARTPC_CUSTOM_BIOS_INPUT2,
};
enum apmf_preq_smartpc {
NOTIFY_CUSTOM_BIOS_INPUT1 = 5,
NOTIFY_CUSTOM_BIOS_INPUT2,
};
/* Command ids for TA communication */
enum ta_pmf_command {
TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE,
@ -657,7 +669,8 @@ struct ta_pmf_condition_info {
u32 power_slider;
u32 lid_state;
bool user_present;
u32 rsvd1[2];
u32 bios_input1;
u32 bios_input2;
u32 monitor_count;
u32 rsvd2[2];
u32 bat_design;
@ -797,7 +810,4 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
/* Quirk infrastructure */
void amd_pmf_quirks_init(struct amd_pmf_dev *dev);
#endif /* PMF_H */

View File

@ -47,12 +47,35 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *
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, "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;
@ -201,4 +224,5 @@ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_tab
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);
}

View File

@ -405,6 +405,8 @@ int amd_pmf_init_sps(struct amd_pmf_dev *dev)
amd_pmf_set_sps_power_limits(dev);
}
dev->pprof.name = "amd-pmf";
dev->pprof.dev = dev->dev;
dev->pprof.profile_get = amd_pmf_profile_get;
dev->pprof.profile_set = amd_pmf_profile_set;
@ -424,5 +426,5 @@ int amd_pmf_init_sps(struct amd_pmf_dev *dev)
void amd_pmf_deinit_sps(struct amd_pmf_dev *dev)
{
platform_profile_remove();
platform_profile_remove(&dev->pprof);
}

View File

@ -3782,7 +3782,7 @@ static ssize_t throttle_thermal_policy_store(struct device *dev,
* Ensure that platform_profile updates userspace with the change to ensure
* that platform_profile and throttle_thermal_policy_mode are in sync.
*/
platform_profile_notify();
platform_profile_notify(&asus->platform_profile_handler);
return count;
}
@ -3869,6 +3869,8 @@ static int platform_profile_setup(struct asus_wmi *asus)
dev_info(dev, "Using throttle_thermal_policy for platform_profile support\n");
asus->platform_profile_handler.name = "asus-wmi";
asus->platform_profile_handler.dev = dev;
asus->platform_profile_handler.profile_get = asus_wmi_platform_profile_get;
asus->platform_profile_handler.profile_set = asus_wmi_platform_profile_set;
@ -4843,7 +4845,7 @@ static int asus_wmi_add(struct platform_device *pdev)
fail_custom_fan_curve:
fail_platform_profile_setup:
if (asus->platform_profile_support)
platform_profile_remove();
platform_profile_remove(&asus->platform_profile_handler);
fail_fan_boost_mode:
fail_platform:
kfree(asus);
@ -4870,7 +4872,7 @@ static void asus_wmi_remove(struct platform_device *device)
asus_wmi_battery_exit(asus);
if (asus->platform_profile_support)
platform_profile_remove();
platform_profile_remove(&asus->platform_profile_handler);
kfree(asus);
}

View File

@ -152,6 +152,7 @@ config DELL_SMBIOS_SMM
config DELL_SMO8800
tristate "Dell Latitude freefall driver (ACPI SMO88XX)"
default m
depends on I2C
depends on ACPI || COMPILE_TEST
help
Say Y here if you want to support SMO88XX freefall devices

View File

@ -15,6 +15,7 @@ dell-smbios-objs := dell-smbios-base.o
dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o
dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o
obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o
obj-$(CONFIG_DELL_SMO8800) += dell-lis3lv02d.o
obj-$(CONFIG_DELL_UART_BACKLIGHT) += dell-uart-backlight.o
obj-$(CONFIG_DELL_WMI) += dell-wmi.o
dell-wmi-objs := dell-wmi-base.o

View File

@ -1120,7 +1120,7 @@ static int thermal_profile_set(struct platform_profile_handler *pprof,
return wmax_thermal_control(supported_thermal_profiles[profile]);
}
static int create_thermal_profile(void)
static int create_thermal_profile(struct platform_device *platform_device)
{
u32 out_data;
u8 sys_desc[4];
@ -1168,6 +1168,8 @@ static int create_thermal_profile(void)
pp_handler.profile_get = thermal_profile_get;
pp_handler.profile_set = thermal_profile_set;
pp_handler.name = "alienware-wmi";
pp_handler.dev = &platform_device->dev;
return platform_profile_register(&pp_handler);
}
@ -1175,7 +1177,7 @@ static int create_thermal_profile(void)
static void remove_thermal_profile(void)
{
if (quirks->thermal)
platform_profile_remove();
platform_profile_remove(&pp_handler);
}
static int __init alienware_wmi_init(void)
@ -1236,7 +1238,7 @@ static int __init alienware_wmi_init(void)
}
if (quirks->thermal) {
ret = create_thermal_profile();
ret = create_thermal_profile(platform_device);
if (ret)
goto fail_prep_thermal_profile;
}

View File

@ -163,7 +163,7 @@ static ssize_t smi_data_buf_size_store(struct device *dev,
}
static ssize_t smi_data_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
const struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
ssize_t ret;
@ -176,7 +176,7 @@ static ssize_t smi_data_read(struct file *filp, struct kobject *kobj,
}
static ssize_t smi_data_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
const struct bin_attribute *bin_attr,
char *buf, loff_t pos, size_t count)
{
ssize_t ret;
@ -636,9 +636,9 @@ static struct notifier_block dcdbas_reboot_nb = {
.priority = INT_MIN
};
static DCDBAS_BIN_ATTR_RW(smi_data);
static const BIN_ATTR_ADMIN_RW(smi_data, 0);
static struct bin_attribute *dcdbas_bin_attrs[] = {
static const struct bin_attribute *const dcdbas_bin_attrs[] = {
&bin_attr_smi_data,
NULL
};
@ -662,7 +662,7 @@ static struct attribute *dcdbas_dev_attrs[] = {
static const struct attribute_group dcdbas_attr_group = {
.attrs = dcdbas_dev_attrs,
.bin_attrs = dcdbas_bin_attrs,
.bin_attrs_new = dcdbas_bin_attrs,
};
static int dcdbas_probe(struct platform_device *dev)

View File

@ -56,14 +56,6 @@
#define DCDBAS_DEV_ATTR_WO(_name) \
DEVICE_ATTR(_name,0200,NULL,_name##_store);
#define DCDBAS_BIN_ATTR_RW(_name) \
struct bin_attribute bin_attr_##_name = { \
.attr = { .name = __stringify(_name), \
.mode = 0600 }, \
.read = _name##_read, \
.write = _name##_write, \
}
struct smi_cmd {
__u32 magic;
__u32 ebx;

View File

@ -0,0 +1,207 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* lis3lv02d i2c-client instantiation for ACPI SMO88xx devices without I2C resources.
*
* Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device/bus.h>
#include <linux/dmi.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include "dell-smo8800-ids.h"
#define DELL_LIS3LV02D_DMI_ENTRY(product_name, i2c_addr) \
{ \
.matches = { \
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), \
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, product_name), \
}, \
.driver_data = (void *)(uintptr_t)(i2c_addr), \
}
/*
* Accelerometer's I2C address is not specified in DMI nor ACPI,
* so it is needed to define mapping table based on DMI product names.
*/
static const struct dmi_system_id lis3lv02d_devices[] __initconst = {
/*
* Dell platform team told us that these Latitude devices have
* ST microelectronics accelerometer at I2C address 0x29.
*/
DELL_LIS3LV02D_DMI_ENTRY("Latitude E5250", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E5450", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E5550", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E6440", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E6440 ATG", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E6540", 0x29),
/*
* Additional individual entries were added after verification.
*/
DELL_LIS3LV02D_DMI_ENTRY("Latitude 5480", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E6330", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E6430", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Precision 3540", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Vostro V131", 0x1d),
DELL_LIS3LV02D_DMI_ENTRY("Vostro 5568", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("XPS 15 7590", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("XPS 15 9550", 0x29),
{ }
};
static u8 i2c_addr;
static struct i2c_client *i2c_dev;
static bool notifier_registered;
static bool i2c_adapter_is_main_i801(struct i2c_adapter *adap)
{
/*
* Only match the main I801 adapter and reject secondary adapters
* which names start with "SMBus I801 IDF adapter".
*/
return strstarts(adap->name, "SMBus I801 adapter");
}
static int find_i801(struct device *dev, void *data)
{
struct i2c_adapter *adap, **adap_ret = data;
adap = i2c_verify_adapter(dev);
if (!adap)
return 0;
if (!i2c_adapter_is_main_i801(adap))
return 0;
*adap_ret = i2c_get_adapter(adap->nr);
return 1;
}
static void instantiate_i2c_client(struct work_struct *work)
{
struct i2c_board_info info = { };
struct i2c_adapter *adap = NULL;
if (i2c_dev)
return;
/*
* bus_for_each_dev() and not i2c_for_each_dev() to avoid
* a deadlock when find_i801() calls i2c_get_adapter().
*/
bus_for_each_dev(&i2c_bus_type, NULL, &adap, find_i801);
if (!adap)
return;
info.addr = i2c_addr;
strscpy(info.type, "lis3lv02d", I2C_NAME_SIZE);
i2c_dev = i2c_new_client_device(adap, &info);
if (IS_ERR(i2c_dev)) {
dev_err(&adap->dev, "error %ld registering i2c_client\n", PTR_ERR(i2c_dev));
i2c_dev = NULL;
} else {
dev_dbg(&adap->dev, "registered lis3lv02d on address 0x%02x\n", info.addr);
}
i2c_put_adapter(adap);
}
static DECLARE_WORK(i2c_work, instantiate_i2c_client);
static int i2c_bus_notify(struct notifier_block *nb, unsigned long action, void *data)
{
struct device *dev = data;
struct i2c_client *client;
struct i2c_adapter *adap;
switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
adap = i2c_verify_adapter(dev);
if (!adap)
break;
if (i2c_adapter_is_main_i801(adap))
queue_work(system_long_wq, &i2c_work);
break;
case BUS_NOTIFY_REMOVED_DEVICE:
client = i2c_verify_client(dev);
if (!client)
break;
if (i2c_dev == client) {
dev_dbg(&client->adapter->dev, "lis3lv02d i2c_client removed\n");
i2c_dev = NULL;
}
break;
default:
break;
}
return 0;
}
static struct notifier_block i2c_nb = { .notifier_call = i2c_bus_notify };
static int __init match_acpi_device_ids(struct device *dev, const void *data)
{
return acpi_match_device(data, dev) ? 1 : 0;
}
static int __init dell_lis3lv02d_init(void)
{
const struct dmi_system_id *lis3lv02d_dmi_id;
struct device *dev;
int err;
/*
* First check for a matching platform_device. This protects against
* SMO88xx ACPI fwnodes which actually do have an I2C resource, which
* will already have an i2c_client instantiated (not a platform_device).
*/
dev = bus_find_device(&platform_bus_type, NULL, smo8800_ids, match_acpi_device_ids);
if (!dev) {
pr_debug("No SMO88xx platform-device found\n");
return 0;
}
put_device(dev);
lis3lv02d_dmi_id = dmi_first_match(lis3lv02d_devices);
if (!lis3lv02d_dmi_id) {
pr_warn("accelerometer is present on SMBus but its address is unknown, skipping registration\n");
return 0;
}
i2c_addr = (long)lis3lv02d_dmi_id->driver_data;
/*
* Register i2c-bus notifier + queue initial scan for lis3lv02d
* i2c_client instantiation.
*/
err = bus_register_notifier(&i2c_bus_type, &i2c_nb);
if (err)
return err;
notifier_registered = true;
queue_work(system_long_wq, &i2c_work);
return 0;
}
module_init(dell_lis3lv02d_init);
static void __exit dell_lis3lv02d_module_exit(void)
{
if (!notifier_registered)
return;
bus_unregister_notifier(&i2c_bus_type, &i2c_nb);
cancel_work_sync(&i2c_work);
i2c_unregister_device(i2c_dev);
}
module_exit(dell_lis3lv02d_module_exit);
MODULE_DESCRIPTION("lis3lv02d i2c-client instantiation for ACPI SMO88xx devices");
MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_LICENSE("GPL");

View File

@ -18,10 +18,13 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_profile.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "dell-smbios.h"
static struct platform_device *platform_device;
static const struct dmi_system_id dell_device_table[] __initconst = {
{
.ident = "Dell Inc.",
@ -244,9 +247,17 @@ static int thermal_init(void)
if (!supported_modes)
return 0;
thermal_handler = kzalloc(sizeof(*thermal_handler), GFP_KERNEL);
if (!thermal_handler)
return -ENOMEM;
platform_device = platform_device_register_simple("dell-pc", PLATFORM_DEVID_NONE, NULL, 0);
if (IS_ERR(platform_device))
return PTR_ERR(platform_device);
thermal_handler = devm_kzalloc(&platform_device->dev, sizeof(*thermal_handler), GFP_KERNEL);
if (!thermal_handler) {
ret = -ENOMEM;
goto cleanup_platform_device;
}
thermal_handler->name = "dell-pc";
thermal_handler->dev = &platform_device->dev;
thermal_handler->profile_get = thermal_platform_profile_get;
thermal_handler->profile_set = thermal_platform_profile_set;
@ -261,20 +272,25 @@ static int thermal_init(void)
/* Clean up if failed */
ret = platform_profile_register(thermal_handler);
if (ret) {
kfree(thermal_handler);
thermal_handler = NULL;
}
if (ret)
goto cleanup_thermal_handler;
return 0;
cleanup_thermal_handler:
thermal_handler = NULL;
cleanup_platform_device:
platform_device_unregister(platform_device);
return ret;
}
static void thermal_cleanup(void)
{
if (thermal_handler) {
platform_profile_remove();
kfree(thermal_handler);
}
if (thermal_handler)
platform_profile_remove(thermal_handler);
platform_device_unregister(platform_device);
}
static int __init dell_init(void)

View File

@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ACPI SMO88XX lis3lv02d freefall / accelerometer device-ids.
*
* Copyright (C) 2012 Sonal Santan <sonal.santan@gmail.com>
* Copyright (C) 2014 Pali Rohár <pali@kernel.org>
*/
#ifndef _DELL_SMO8800_IDS_H_
#define _DELL_SMO8800_IDS_H_
#include <linux/mod_devicetable.h>
#include <linux/module.h>
static const struct acpi_device_id smo8800_ids[] = {
{ "SMO8800" },
{ "SMO8801" },
{ "SMO8810" },
{ "SMO8811" },
{ "SMO8820" },
{ "SMO8821" },
{ "SMO8830" },
{ "SMO8831" },
{ }
};
MODULE_DEVICE_TABLE(acpi, smo8800_ids);
#endif

View File

@ -14,10 +14,10 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include "dell-smo8800-ids.h"
struct smo8800_device {
u32 irq; /* acpi device irq */
@ -163,20 +163,6 @@ static void smo8800_remove(struct platform_device *device)
dev_dbg(&device->dev, "device /dev/freefall unregistered\n");
}
/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */
static const struct acpi_device_id smo8800_ids[] = {
{ "SMO8800", 0 },
{ "SMO8801", 0 },
{ "SMO8810", 0 },
{ "SMO8811", 0 },
{ "SMO8820", 0 },
{ "SMO8821", 0 },
{ "SMO8830", 0 },
{ "SMO8831", 0 },
{ "", 0 },
};
MODULE_DEVICE_TABLE(acpi, smo8800_ids);
static struct platform_driver smo8800_driver = {
.probe = smo8800_probe,
.remove = smo8800_remove,

View File

@ -159,7 +159,7 @@ static int dell_uart_set_bl_power(struct dell_uart_backlight *dell_bl, int power
set_power[0] = DELL_SOF(SET_CMD_LEN);
set_power[1] = CMD_SET_BL_POWER;
set_power[2] = (power == FB_BLANK_UNBLANK) ? 1 : 0;
set_power[2] = (power == BACKLIGHT_POWER_ON) ? 1 : 0;
set_power[3] = dell_uart_checksum(set_power, 3);
ret = dell_uart_bl_command(dell_bl, set_power, SET_CMD_LEN, resp, SET_RESP_LEN);

View File

@ -475,7 +475,7 @@ static ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count)
}
static ssize_t data_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
const struct bin_attribute *bin_attr,
char *buffer, loff_t pos, size_t count)
{
ssize_t ret_count = 0;
@ -492,7 +492,7 @@ static ssize_t data_read(struct file *filp, struct kobject *kobj,
spin_unlock(&rbu_data.lock);
return ret_count;
}
static BIN_ATTR_RO(data, 0);
static const BIN_ATTR_RO(data, 0);
static void callbackfn_rbu(const struct firmware *fw, void *context)
{
@ -530,7 +530,7 @@ static void callbackfn_rbu(const struct firmware *fw, void *context)
}
static ssize_t image_type_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
const struct bin_attribute *bin_attr,
char *buffer, loff_t pos, size_t count)
{
int size = 0;
@ -540,7 +540,7 @@ static ssize_t image_type_read(struct file *filp, struct kobject *kobj,
}
static ssize_t image_type_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
const struct bin_attribute *bin_attr,
char *buffer, loff_t pos, size_t count)
{
int rc = count;
@ -597,10 +597,10 @@ static ssize_t image_type_write(struct file *filp, struct kobject *kobj,
return rc;
}
static BIN_ATTR_RW(image_type, 0);
static const BIN_ATTR_RW(image_type, 0);
static ssize_t packet_size_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
const struct bin_attribute *bin_attr,
char *buffer, loff_t pos, size_t count)
{
int size = 0;
@ -613,7 +613,7 @@ static ssize_t packet_size_read(struct file *filp, struct kobject *kobj,
}
static ssize_t packet_size_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
const struct bin_attribute *bin_attr,
char *buffer, loff_t pos, size_t count)
{
unsigned long temp;
@ -626,9 +626,9 @@ static ssize_t packet_size_write(struct file *filp, struct kobject *kobj,
spin_unlock(&rbu_data.lock);
return count;
}
static BIN_ATTR_RW(packet_size, 0);
static const BIN_ATTR_RW(packet_size, 0);
static struct bin_attribute *rbu_bin_attrs[] = {
static const struct bin_attribute *const rbu_bin_attrs[] = {
&bin_attr_data,
&bin_attr_image_type,
&bin_attr_packet_size,
@ -636,7 +636,7 @@ static struct bin_attribute *rbu_bin_attrs[] = {
};
static const struct attribute_group rbu_group = {
.bin_attrs = rbu_bin_attrs,
.bin_attrs_new = rbu_bin_attrs,
};
static int __init dcdrbu_init(void)

View File

@ -505,8 +505,8 @@ static int acpi_fujitsu_bl_add(struct acpi_device *device)
return -ENOMEM;
fujitsu_bl = priv;
strcpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME);
strcpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME);
strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
device->driver_data = priv;
pr_info("ACPI: %s [%s]\n",
@ -891,8 +891,8 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device)
WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended.");
fext = device;
strcpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME);
strcpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME);
strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
device->driver_data = priv;
/* kfifo */

View File

@ -1565,7 +1565,7 @@ static inline void omen_unregister_powersource_event_handler(void)
unregister_acpi_notifier(&platform_power_source_nb);
}
static int thermal_profile_setup(void)
static int thermal_profile_setup(struct platform_device *device)
{
int err, tp;
@ -1624,6 +1624,8 @@ static int thermal_profile_setup(void)
set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices);
}
platform_profile_handler.name = "hp-wmi";
platform_profile_handler.dev = &device->dev;
set_bit(PLATFORM_PROFILE_BALANCED, platform_profile_handler.choices);
set_bit(PLATFORM_PROFILE_PERFORMANCE, platform_profile_handler.choices);
@ -1663,7 +1665,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
if (err < 0)
return err;
thermal_profile_setup();
thermal_profile_setup(device);
return 0;
}
@ -1691,7 +1693,7 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device)
}
if (platform_profile_support)
platform_profile_remove();
platform_profile_remove(&platform_profile_handler);
}
static int hp_wmi_resume_handler(struct device *device)

View File

@ -1041,7 +1041,7 @@ static void dytc_profile_refresh(struct ideapad_private *priv)
if (profile != priv->dytc->current_profile) {
priv->dytc->current_profile = profile;
platform_profile_notify();
platform_profile_notify(&priv->dytc->pprof);
}
}
@ -1102,6 +1102,8 @@ static int ideapad_dytc_profile_init(struct ideapad_private *priv)
mutex_init(&priv->dytc->mutex);
priv->dytc->pprof.name = "ideapad-laptop";
priv->dytc->pprof.dev = &priv->platform_device->dev;
priv->dytc->priv = priv;
priv->dytc->pprof.profile_get = dytc_profile_get;
priv->dytc->pprof.profile_set = dytc_profile_set;
@ -1134,7 +1136,7 @@ static void ideapad_dytc_profile_exit(struct ideapad_private *priv)
if (!priv->dytc)
return;
platform_profile_remove();
platform_profile_remove(&priv->dytc->pprof);
mutex_destroy(&priv->dytc->mutex);
kfree(priv->dytc);

View File

@ -177,6 +177,8 @@ static int inspur_wmi_probe(struct wmi_device *wdev, const void *context)
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
priv->handler.name = "inspur-wmi";
priv->handler.dev = &wdev->dev;
priv->handler.profile_get = inspur_platform_profile_get;
priv->handler.profile_set = inspur_platform_profile_set;
@ -189,7 +191,10 @@ static int inspur_wmi_probe(struct wmi_device *wdev, const void *context)
static void inspur_wmi_remove(struct wmi_device *wdev)
{
platform_profile_remove();
struct inspur_wmi_priv *priv;
priv = dev_get_drvdata(&wdev->dev);
platform_profile_remove(&priv->handler);
}
static const struct wmi_device_id inspur_wmi_id_table[] = {

View File

@ -83,6 +83,7 @@ config INTEL_BXTWC_PMIC_TMU
config INTEL_BYTCRC_PWRSRC
tristate "Intel Bay Trail Crystal Cove power source driver"
depends on INTEL_SOC_PMIC
depends on POWER_SUPPLY
help
This option adds a power source driver for Crystal Cove PMICs
on Intel Bay Trail devices.

View File

@ -8,13 +8,22 @@
* Copyright (C) 2013 Intel Corporation
*/
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/interrupt.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/regmap.h>
#define CRYSTALCOVE_PWRSRC_IRQ 0x03
#define CRYSTALCOVE_SPWRSRC_REG 0x1E
#define CRYSTALCOVE_SPWRSRC_USB BIT(0)
#define CRYSTALCOVE_SPWRSRC_DC BIT(1)
#define CRYSTALCOVE_SPWRSRC_BATTERY BIT(2)
#define CRYSTALCOVE_RESETSRC0_REG 0x20
#define CRYSTALCOVE_RESETSRC1_REG 0x21
#define CRYSTALCOVE_WAKESRC_REG 0x22
@ -22,6 +31,7 @@
struct crc_pwrsrc_data {
struct regmap *regmap;
struct dentry *debug_dentry;
struct power_supply *psy;
unsigned int resetsrc0;
unsigned int resetsrc1;
unsigned int wakesrc;
@ -118,13 +128,60 @@ static int crc_pwrsrc_read_and_clear(struct crc_pwrsrc_data *data,
return regmap_write(data->regmap, reg, *val);
}
static irqreturn_t crc_pwrsrc_irq_handler(int irq, void *_data)
{
struct crc_pwrsrc_data *data = _data;
unsigned int irq_mask;
if (regmap_read(data->regmap, CRYSTALCOVE_PWRSRC_IRQ, &irq_mask))
return IRQ_NONE;
regmap_write(data->regmap, CRYSTALCOVE_PWRSRC_IRQ, irq_mask);
power_supply_changed(data->psy);
return IRQ_HANDLED;
}
static int crc_pwrsrc_psy_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct crc_pwrsrc_data *data = power_supply_get_drvdata(psy);
unsigned int pwrsrc;
int ret;
if (psp != POWER_SUPPLY_PROP_ONLINE)
return -EINVAL;
ret = regmap_read(data->regmap, CRYSTALCOVE_SPWRSRC_REG, &pwrsrc);
if (ret)
return ret;
val->intval = !!(pwrsrc & (CRYSTALCOVE_SPWRSRC_USB |
CRYSTALCOVE_SPWRSRC_DC));
return 0;
}
static const enum power_supply_property crc_pwrsrc_psy_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static const struct power_supply_desc crc_pwrsrc_psy_desc = {
.name = "crystal_cove_pwrsrc",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = crc_pwrsrc_psy_props,
.num_properties = ARRAY_SIZE(crc_pwrsrc_psy_props),
.get_property = crc_pwrsrc_psy_get_property,
};
static int crc_pwrsrc_probe(struct platform_device *pdev)
{
struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct crc_pwrsrc_data *data;
int ret;
int irq, ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
@ -149,6 +206,24 @@ static int crc_pwrsrc_probe(struct platform_device *pdev)
if (ret)
return ret;
if (device_property_read_bool(dev->parent, "linux,register-pwrsrc-power_supply")) {
struct power_supply_config psy_cfg = { .drv_data = data };
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
data->psy = devm_power_supply_register(dev, &crc_pwrsrc_psy_desc, &psy_cfg);
if (IS_ERR(data->psy))
return dev_err_probe(dev, PTR_ERR(data->psy), "registering power-supply\n");
ret = devm_request_threaded_irq(dev, irq, NULL,
crc_pwrsrc_irq_handler,
IRQF_ONESHOT, KBUILD_MODNAME, data);
if (ret)
return dev_err_probe(dev, ret, "requesting IRQ\n");
}
data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL);
debugfs_create_file("pwrsrc", 0444, data->debug_dentry, data, &pwrsrc_fops);
debugfs_create_file("resetsrc", 0444, data->debug_dentry, data, &resetsrc_fops);

View File

@ -83,8 +83,12 @@ static void int0002_irq_ack(struct irq_data *data)
static void int0002_irq_unmask(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
irq_hw_number_t hwirq = irqd_to_hwirq(data);
u32 gpe_en_reg;
gpiochip_enable_irq(gc, hwirq);
gpe_en_reg = inl(GPE0A_EN_PORT);
gpe_en_reg |= GPE0A_PME_B0_EN_BIT;
outl(gpe_en_reg, GPE0A_EN_PORT);
@ -92,11 +96,15 @@ static void int0002_irq_unmask(struct irq_data *data)
static void int0002_irq_mask(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
irq_hw_number_t hwirq = irqd_to_hwirq(data);
u32 gpe_en_reg;
gpe_en_reg = inl(GPE0A_EN_PORT);
gpe_en_reg &= ~GPE0A_PME_B0_EN_BIT;
outl(gpe_en_reg, GPE0A_EN_PORT);
gpiochip_disable_irq(gc, hwirq);
}
static int int0002_irq_set_wake(struct irq_data *data, unsigned int on)
@ -140,12 +148,14 @@ static bool int0002_check_wake(void *data)
return (gpe_sts_reg & GPE0A_PME_B0_STS_BIT);
}
static struct irq_chip int0002_irqchip = {
static const struct irq_chip int0002_irqchip = {
.name = DRV_NAME,
.irq_ack = int0002_irq_ack,
.irq_mask = int0002_irq_mask,
.irq_unmask = int0002_irq_unmask,
.irq_set_wake = int0002_irq_set_wake,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static void int0002_init_irq_valid_mask(struct gpio_chip *chip,
@ -203,7 +213,7 @@ static int int0002_probe(struct platform_device *pdev)
}
girq = &chip->irq;
girq->chip = &int0002_irqchip;
gpio_irq_chip_set_chip(girq, &int0002_irqchip);
/* This let us handle the parent IRQ in the driver */
girq->parent_handler = NULL;
girq->num_parents = 0;

View File

@ -70,6 +70,8 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev,
return -ENODEV;
}
dev_dbg(dev, "Sensor name %s\n", acpi_dev_name(sensor));
*name_ret = devm_kasprintf(dev, GFP_KERNEL, I2C_DEV_NAME_FORMAT,
acpi_dev_name(sensor));
if (!*name_ret)

View File

@ -178,11 +178,11 @@ static void int3472_get_func_and_polarity(u8 type, const char **func, u32 *polar
* to create clocks and regulators via the usual frameworks.
*
* Return:
* * 1 - To continue the loop
* * 0 - When all resources found are handled properly.
* * -EINVAL - If the resource is not a GPIO IO resource
* * -ENODEV - If the resource has no corresponding _DSM entry
* * -Other - Errors propagated from one of the sub-functions.
* * 1 - Continue the loop without adding a copy of the resource to
* * the list passed to acpi_dev_get_resources()
* * 0 - Continue the loop after adding a copy of the resource to
* * the list passed to acpi_dev_get_resources()
* * -errno - Error, break loop
*/
static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
void *data)
@ -220,10 +220,10 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
int3472_get_func_and_polarity(type, &func, &polarity);
pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value);
if (pin != agpio->pin_table[0])
dev_warn(int3472->dev, "%s %s pin number mismatch _DSM %d resource %d\n",
func, agpio->resource_source.string_ptr, pin,
agpio->pin_table[0]);
/* Pin field is not really used under Windows and wraps around at 8 bits */
if (pin != (agpio->pin_table[0] & 0xff))
dev_dbg(int3472->dev, FW_BUG "%s %s pin number mismatch _DSM %d resource %d\n",
func, agpio->resource_source.string_ptr, pin, agpio->pin_table[0]);
active_value = FIELD_GET(INT3472_GPIO_DSM_SENSOR_ON_VAL, obj->integer.value);
if (!active_value)
@ -289,7 +289,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
if (ret < 0)
return dev_err_probe(int3472->dev, ret, err_msg);
return ret;
/* Tell acpi_dev_get_resources() to not make a copy of the resource */
return 1;
}
static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472)
@ -336,6 +337,9 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev)
struct int3472_cldb cldb;
int ret;
if (!adev)
return -ENODEV;
ret = skl_int3472_fill_cldb(adev, &cldb);
if (ret) {
dev_err(&pdev->dev, "Couldn't fill CLDB structure\n");

View File

@ -152,6 +152,9 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client)
int ret;
int i;
if (!adev)
return -ENODEV;
n_consumers = skl_int3472_fill_clk_pdata(&client->dev, &clk_pdata);
if (n_consumers < 0)
return n_consumers;

View File

@ -262,7 +262,7 @@ static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxilia
struct resource *res;
struct tpmi_plr *plr;
void __iomem *base;
char name[16];
char name[17];
int err;
plat_info = tpmi_get_platform_data(auxdev);

View File

@ -81,7 +81,7 @@ EXPORT_SYMBOL_NS_GPL(pmt_telem_read_mmio, "INTEL_PMT");
*/
static ssize_t
intel_pmt_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
const struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct intel_pmt_entry *entry = container_of(attr,
@ -308,7 +308,7 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
entry->pmt_bin_attr.attr.name = ns->name;
entry->pmt_bin_attr.attr.mode = 0440;
entry->pmt_bin_attr.mmap = intel_pmt_mmap;
entry->pmt_bin_attr.read = intel_pmt_read;
entry->pmt_bin_attr.read_new = intel_pmt_read;
entry->pmt_bin_attr.size = entry->size;
ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);

View File

@ -398,8 +398,8 @@ static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count,
}
static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
const struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct sdsi_priv *priv = dev_get_drvdata(dev);
@ -409,11 +409,11 @@ static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj,
return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_AKC);
}
static BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG);
static const BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG);
static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
const struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct sdsi_priv *priv = dev_get_drvdata(dev);
@ -423,7 +423,7 @@ static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj,
return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_CAP);
}
static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG);
static const BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG);
static ssize_t
certificate_read(u64 command, u64 control_flags, struct sdsi_priv *priv,
@ -469,7 +469,7 @@ certificate_read(u64 command, u64 control_flags, struct sdsi_priv *priv,
static ssize_t
state_certificate_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
const struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
@ -477,11 +477,11 @@ state_certificate_read(struct file *filp, struct kobject *kobj,
return certificate_read(SDSI_CMD_READ_STATE, 0, priv, buf, off, count);
}
static BIN_ATTR_ADMIN_RO(state_certificate, SDSI_SIZE_READ_MSG);
static const BIN_ATTR_ADMIN_RO(state_certificate, SDSI_SIZE_READ_MSG);
static ssize_t
meter_certificate_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
const struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
@ -489,11 +489,11 @@ meter_certificate_read(struct file *filp, struct kobject *kobj,
return certificate_read(SDSI_CMD_READ_METER, 0, priv, buf, off, count);
}
static BIN_ATTR_ADMIN_RO(meter_certificate, SDSI_SIZE_READ_MSG);
static const BIN_ATTR_ADMIN_RO(meter_certificate, SDSI_SIZE_READ_MSG);
static ssize_t
meter_current_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
const struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
{
struct device *dev = kobj_to_dev(kobj);
@ -502,11 +502,11 @@ meter_current_read(struct file *filp, struct kobject *kobj,
return certificate_read(SDSI_CMD_READ_METER, CTRL_METER_ENABLE_DRAM,
priv, buf, off, count);
}
static BIN_ATTR_ADMIN_RO(meter_current, SDSI_SIZE_READ_MSG);
static const BIN_ATTR_ADMIN_RO(meter_current, SDSI_SIZE_READ_MSG);
static ssize_t registers_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off,
size_t count)
const struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct sdsi_priv *priv = dev_get_drvdata(dev);
@ -528,9 +528,9 @@ static ssize_t registers_read(struct file *filp, struct kobject *kobj,
return count;
}
static BIN_ATTR_ADMIN_RO(registers, SDSI_SIZE_REGS);
static const BIN_ATTR_ADMIN_RO(registers, SDSI_SIZE_REGS);
static struct bin_attribute *sdsi_bin_attrs[] = {
static const struct bin_attribute *const sdsi_bin_attrs[] = {
&bin_attr_registers,
&bin_attr_state_certificate,
&bin_attr_meter_certificate,
@ -576,7 +576,7 @@ static struct attribute *sdsi_attrs[] = {
static const struct attribute_group sdsi_group = {
.attrs = sdsi_attrs,
.bin_attrs = sdsi_bin_attrs,
.bin_attrs_new = sdsi_bin_attrs,
.is_bin_visible = sdsi_battr_is_visible,
};
__ATTRIBUTE_GROUPS(sdsi);

View File

@ -20,7 +20,6 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeup.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/sysfs.h>

View File

@ -22,32 +22,14 @@
#include <linux/string.h>
static inline struct device *
get_serdev_controller(const char *serial_ctrl_hid,
const char *serial_ctrl_uid,
int serial_ctrl_port,
const char *serdev_ctrl_name)
get_serdev_controller_from_parent(struct device *ctrl_dev,
int serial_ctrl_port,
const char *serdev_ctrl_name)
{
struct device *ctrl_dev, *child;
struct acpi_device *ctrl_adev;
struct device *child;
char name[32];
int i;
ctrl_adev = acpi_dev_get_first_match_dev(serial_ctrl_hid, serial_ctrl_uid, -1);
if (!ctrl_adev) {
pr_err("error could not get %s/%s serial-ctrl adev\n",
serial_ctrl_hid, serial_ctrl_uid);
return ERR_PTR(-ENODEV);
}
/* get_first_physical_node() returns a weak ref */
ctrl_dev = get_device(acpi_get_first_physical_node(ctrl_adev));
if (!ctrl_dev) {
pr_err("error could not get %s/%s serial-ctrl physical node\n",
serial_ctrl_hid, serial_ctrl_uid);
ctrl_dev = ERR_PTR(-ENODEV);
goto put_ctrl_adev;
}
/* Walk host -> uart-ctrl -> port -> serdev-ctrl */
for (i = 0; i < 3; i++) {
switch (i) {
@ -67,14 +49,40 @@ get_serdev_controller(const char *serial_ctrl_hid,
put_device(ctrl_dev);
if (!child) {
pr_err("error could not find '%s' device\n", name);
ctrl_dev = ERR_PTR(-ENODEV);
goto put_ctrl_adev;
return ERR_PTR(-ENODEV);
}
ctrl_dev = child;
}
put_ctrl_adev:
acpi_dev_put(ctrl_adev);
return ctrl_dev;
}
static inline struct device *
get_serdev_controller(const char *serial_ctrl_hid,
const char *serial_ctrl_uid,
int serial_ctrl_port,
const char *serdev_ctrl_name)
{
struct acpi_device *adev;
struct device *parent;
adev = acpi_dev_get_first_match_dev(serial_ctrl_hid, serial_ctrl_uid, -1);
if (!adev) {
pr_err("error could not get %s/%s serial-ctrl adev\n",
serial_ctrl_hid, serial_ctrl_uid ?: "*");
return ERR_PTR(-ENODEV);
}
/* get_first_physical_node() returns a weak ref */
parent = get_device(acpi_get_first_physical_node(adev));
acpi_dev_put(adev);
if (!parent) {
pr_err("error could not get %s/%s serial-ctrl physical node\n",
serial_ctrl_hid, serial_ctrl_uid ?: "*");
return ERR_PTR(-ENODEV);
}
/* This puts our reference on parent and returns a ref on the ctrl */
return get_serdev_controller_from_parent(parent, serial_ctrl_port, serdev_ctrl_name);
}

View File

@ -10538,6 +10538,12 @@ static int dytc_profile_set(struct platform_profile_handler *pprof,
return err;
}
static struct platform_profile_handler dytc_profile = {
.name = "thinkpad-acpi",
.profile_get = dytc_profile_get,
.profile_set = dytc_profile_set,
};
static void dytc_profile_refresh(void)
{
enum platform_profile_option profile;
@ -10566,15 +10572,10 @@ static void dytc_profile_refresh(void)
err = convert_dytc_to_profile(funcmode, perfmode, &profile);
if (!err && profile != dytc_current_profile) {
dytc_current_profile = profile;
platform_profile_notify();
platform_profile_notify(&dytc_profile);
}
}
static struct platform_profile_handler dytc_profile = {
.profile_get = dytc_profile_get,
.profile_set = dytc_profile_set,
};
static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
{
int err, output;
@ -10637,6 +10638,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
dbg_printk(TPACPI_DBG_INIT,
"DYTC version %d: thermal mode available\n", dytc_version);
dytc_profile.dev = &tpacpi_pdev->dev;
/* Create platform_profile structure and register */
err = platform_profile_register(&dytc_profile);
/*
@ -10658,7 +10660,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
static void dytc_profile_exit(void)
{
platform_profile_remove();
platform_profile_remove(&dytc_profile);
}
static struct ibm_struct dytc_profile_driver_data = {

View File

@ -20,66 +20,66 @@
#define WMI_BMOF_GUID "05901221-D566-11D1-B2F0-00A0C9062910"
struct bmof_priv {
union acpi_object *bmofdata;
struct bin_attribute bmof_bin_attr;
};
static ssize_t read_bmof(struct file *filp, struct kobject *kobj, struct bin_attribute *attr,
static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct bmof_priv *priv = container_of(attr, struct bmof_priv, bmof_bin_attr);
struct device *dev = kobj_to_dev(kobj);
union acpi_object *obj = dev_get_drvdata(dev);
return memory_read_from_buffer(buf, count, &off, priv->bmofdata->buffer.pointer,
priv->bmofdata->buffer.length);
return memory_read_from_buffer(buf, count, &off, obj->buffer.pointer, obj->buffer.length);
}
static const BIN_ATTR_ADMIN_RO(bmof, 0);
static const struct bin_attribute * const bmof_attrs[] = {
&bin_attr_bmof,
NULL
};
static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
union acpi_object *obj = dev_get_drvdata(dev);
return obj->buffer.length;
}
static const struct attribute_group bmof_group = {
.bin_size = bmof_bin_size,
.bin_attrs_new = bmof_attrs,
};
static const struct attribute_group *bmof_groups[] = {
&bmof_group,
NULL
};
static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
{
struct bmof_priv *priv;
int ret;
union acpi_object *obj;
priv = devm_kzalloc(&wdev->dev, sizeof(struct bmof_priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
dev_set_drvdata(&wdev->dev, priv);
priv->bmofdata = wmidev_block_query(wdev, 0);
if (!priv->bmofdata) {
obj = wmidev_block_query(wdev, 0);
if (!obj) {
dev_err(&wdev->dev, "failed to read Binary MOF\n");
return -EIO;
}
if (priv->bmofdata->type != ACPI_TYPE_BUFFER) {
if (obj->type != ACPI_TYPE_BUFFER) {
dev_err(&wdev->dev, "Binary MOF is not a buffer\n");
ret = -EIO;
goto err_free;
kfree(obj);
return -EIO;
}
sysfs_bin_attr_init(&priv->bmof_bin_attr);
priv->bmof_bin_attr.attr.name = "bmof";
priv->bmof_bin_attr.attr.mode = 0400;
priv->bmof_bin_attr.read = read_bmof;
priv->bmof_bin_attr.size = priv->bmofdata->buffer.length;
ret = device_create_bin_file(&wdev->dev, &priv->bmof_bin_attr);
if (ret)
goto err_free;
dev_set_drvdata(&wdev->dev, obj);
return 0;
err_free:
kfree(priv->bmofdata);
return ret;
}
static void wmi_bmof_remove(struct wmi_device *wdev)
{
struct bmof_priv *priv = dev_get_drvdata(&wdev->dev);
union acpi_object *obj = dev_get_drvdata(&wdev->dev);
device_remove_bin_file(&wdev->dev, &priv->bmof_bin_attr);
kfree(priv->bmofdata);
kfree(obj);
}
static const struct wmi_device_id wmi_bmof_id_table[] = {
@ -90,6 +90,7 @@ static const struct wmi_device_id wmi_bmof_id_table[] = {
static struct wmi_driver wmi_bmof_driver = {
.driver = {
.name = "wmi-bmof",
.dev_groups = bmof_groups,
},
.probe = wmi_bmof_probe,
.remove = wmi_bmof_remove,

View File

@ -3,7 +3,7 @@
# X86 Android tablet support Makefile
#
obj-$(CONFIG_X86_ANDROID_TABLETS) += vexia_atla10_ec.o
obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o
x86-android-tablets-y := core.o dmi.o shared-psy-info.o \
asus.o lenovo.o other.o

View File

@ -145,8 +145,8 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst =
static const struct x86_serdev_info asus_me176c_serdevs[] __initconst = {
{
.ctrl_hid = "80860F0A",
.ctrl_uid = "2",
.ctrl.acpi.hid = "80860F0A",
.ctrl.acpi.uid = "2",
.ctrl_devname = "serial0",
.serdev_hid = "BCM2E3A",
},

View File

@ -157,7 +157,7 @@ static struct gpiod_lookup_table * const *gpiod_lookup_tables;
static const struct software_node *bat_swnode;
static void (*exit_handler)(void);
static struct i2c_adapter *
static __init struct i2c_adapter *
get_i2c_adap_by_handle(const struct x86_i2c_client_info *client_info)
{
acpi_handle handle;
@ -177,7 +177,7 @@ static __init int match_parent(struct device *dev, const void *data)
return dev->parent == data;
}
static struct i2c_adapter *
static __init struct i2c_adapter *
get_i2c_adap_by_pci_parent(const struct x86_i2c_client_info *client_info)
{
struct i2c_adapter *adap = NULL;
@ -212,7 +212,7 @@ static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info
if (board_info.irq < 0)
return board_info.irq;
if (dev_info->use_pci_devname)
if (dev_info->use_pci)
adap = get_i2c_adap_by_pci_parent(client_info);
else
adap = get_i2c_adap_by_handle(client_info);
@ -271,15 +271,32 @@ static __init int x86_instantiate_spi_dev(const struct x86_dev_info *dev_info, i
return 0;
}
static __init int x86_instantiate_serdev(const struct x86_serdev_info *info, int idx)
static __init struct device *
get_serdev_controller_by_pci_parent(const struct x86_serdev_info *info)
{
struct pci_dev *pdev;
pdev = pci_get_domain_bus_and_slot(0, 0, info->ctrl.pci.devfn);
if (!pdev)
return ERR_PTR(-EPROBE_DEFER);
/* This puts our reference on pdev and returns a ref on the ctrl */
return get_serdev_controller_from_parent(&pdev->dev, 0, info->ctrl_devname);
}
static __init int x86_instantiate_serdev(const struct x86_dev_info *dev_info, int idx)
{
const struct x86_serdev_info *info = &dev_info->serdev_info[idx];
struct acpi_device *serdev_adev;
struct serdev_device *serdev;
struct device *ctrl_dev;
int ret = -ENODEV;
ctrl_dev = get_serdev_controller(info->ctrl_hid, info->ctrl_uid, 0,
info->ctrl_devname);
if (dev_info->use_pci)
ctrl_dev = get_serdev_controller_by_pci_parent(info);
else
ctrl_dev = get_serdev_controller(info->ctrl.acpi.hid, info->ctrl.acpi.uid,
0, info->ctrl_devname);
if (IS_ERR(ctrl_dev))
return PTR_ERR(ctrl_dev);
@ -446,7 +463,7 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
serdev_count = dev_info->serdev_count;
for (i = 0; i < serdev_count; i++) {
ret = x86_instantiate_serdev(&dev_info->serdev_info[i], i);
ret = x86_instantiate_serdev(dev_info, i);
if (ret < 0) {
x86_android_tablet_remove(pdev);
return ret;

View File

@ -178,8 +178,8 @@ static const struct platform_device_info lenovo_yb1_x90_pdevs[] __initconst = {
*/
static const struct x86_serdev_info lenovo_yb1_x90_serdevs[] __initconst = {
{
.ctrl_hid = "8086228A",
.ctrl_uid = "1",
.ctrl.acpi.hid = "8086228A",
.ctrl.acpi.uid = "1",
.ctrl_devname = "serial0",
.serdev_hid = "BCM2E1A",
},
@ -601,7 +601,7 @@ static const struct regulator_init_data lenovo_yoga_tab2_1380_bq24190_vbus_init_
.num_consumer_supplies = 1,
};
struct bq24190_platform_data lenovo_yoga_tab2_1380_bq24190_pdata = {
static struct bq24190_platform_data lenovo_yoga_tab2_1380_bq24190_pdata = {
.regulator_init_data = &lenovo_yoga_tab2_1380_bq24190_vbus_init_data,
};
@ -726,7 +726,7 @@ static const struct platform_device_info lenovo_yoga_tab2_1380_pdevs[] __initcon
},
};
const char * const lenovo_yoga_tab2_1380_modules[] __initconst = {
static const char * const lenovo_yoga_tab2_1380_modules[] __initconst = {
"bq24190_charger", /* For the Vbus regulator for lc824206xa */
NULL
};

View File

@ -602,14 +602,14 @@ const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
* Vexia EDU ATLA 10 tablet, Android 4.2 / 4.4 + Guadalinex Ubuntu tablet
* distributed to schools in the Spanish Andalucía region.
*/
const char * const crystal_cove_pwrsrc_psy[] = { "crystal_cove_pwrsrc" };
static const char * const crystal_cove_pwrsrc_psy[] = { "crystal_cove_pwrsrc" };
static const struct property_entry vexia_edu_atla10_ulpmc_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", crystal_cove_pwrsrc_psy),
{ }
};
const struct software_node vexia_edu_atla10_ulpmc_node = {
static const struct software_node vexia_edu_atla10_ulpmc_node = {
.properties = vexia_edu_atla10_ulpmc_props,
};
@ -715,6 +715,14 @@ static const struct x86_i2c_client_info vexia_edu_atla10_i2c_clients[] __initcon
}
};
static const struct x86_serdev_info vexia_edu_atla10_serdevs[] __initconst = {
{
.ctrl.pci.devfn = PCI_DEVFN(0x1e, 3),
.ctrl_devname = "serial0",
.serdev_hid = "OBDA8723",
},
};
static struct gpiod_lookup_table vexia_edu_atla10_ft5416_gpios = {
.dev_id = "i2c-FTSC1000",
.table = {
@ -755,9 +763,11 @@ static int __init vexia_edu_atla10_init(struct device *dev)
const struct x86_dev_info vexia_edu_atla10_info __initconst = {
.i2c_client_info = vexia_edu_atla10_i2c_clients,
.i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_i2c_clients),
.serdev_info = vexia_edu_atla10_serdevs,
.serdev_count = ARRAY_SIZE(vexia_edu_atla10_serdevs),
.gpiod_lookup_tables = vexia_edu_atla10_gpios,
.init = vexia_edu_atla10_init,
.use_pci_devname = true,
.use_pci = true,
};
/*

View File

@ -0,0 +1,261 @@
// SPDX-License-Identifier: GPL-2.0
/*
* power_supply class (battery) driver for the I2C attached embedded controller
* found on Vexia EDU ATLA 10 (9V version) tablets.
*
* This is based on the ACPI Battery device in the DSDT which should work
* expect that it expects the I2C controller to be enumerated as an ACPI
* device and the tablet's BIOS enumerates all LPSS devices as PCI devices
* (and changing the LPSS BIOS settings from PCI -> ACPI does not work).
*
* Copyright (c) 2024 Hans de Goede <hansg@kernel.org>
*/
#include <linux/bits.h>
#include <linux/devm-helpers.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <asm/byteorder.h>
/* State field uses ACPI Battery spec status bits */
#define ACPI_BATTERY_STATE_DISCHARGING BIT(0)
#define ACPI_BATTERY_STATE_CHARGING BIT(1)
#define ATLA10_EC_BATTERY_STATE_COMMAND 0x87
#define ATLA10_EC_BATTERY_INFO_COMMAND 0x88
/* From broken ACPI battery device in DSDT */
#define ATLA10_EC_VOLTAGE_MIN_DESIGN_uV 3750000
/* Update data every 5 seconds */
#define UPDATE_INTERVAL_JIFFIES (5 * HZ)
struct atla10_ec_battery_state {
u8 status; /* Using ACPI Battery spec status bits */
u8 capacity; /* Percent */
__le16 charge_now_mAh;
__le16 voltage_now_mV;
__le16 current_now_mA;
__le16 charge_full_mAh;
__le16 temp; /* centi degrees Celsius */
} __packed;
struct atla10_ec_battery_info {
__le16 charge_full_design_mAh;
__le16 voltage_now_mV; /* Should be design voltage, but is not ? */
__le16 charge_full_design2_mAh;
} __packed;
struct atla10_ec_data {
struct i2c_client *client;
struct power_supply *psy;
struct delayed_work work;
struct mutex update_lock;
struct atla10_ec_battery_info info;
struct atla10_ec_battery_state state;
bool valid; /* true if state is valid */
unsigned long last_update; /* In jiffies */
};
static int atla10_ec_cmd(struct atla10_ec_data *data, u8 cmd, u8 len, u8 *values)
{
struct device *dev = &data->client->dev;
u8 buf[I2C_SMBUS_BLOCK_MAX];
int ret;
ret = i2c_smbus_read_block_data(data->client, cmd, buf);
if (ret != len) {
dev_err(dev, "I2C command 0x%02x error: %d\n", cmd, ret);
return -EIO;
}
memcpy(values, buf, len);
return 0;
}
static int atla10_ec_update(struct atla10_ec_data *data)
{
int ret;
if (data->valid && time_before(jiffies, data->last_update + UPDATE_INTERVAL_JIFFIES))
return 0;
ret = atla10_ec_cmd(data, ATLA10_EC_BATTERY_STATE_COMMAND,
sizeof(data->state), (u8 *)&data->state);
if (ret)
return ret;
data->last_update = jiffies;
data->valid = true;
return 0;
}
static int atla10_ec_psy_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct atla10_ec_data *data = power_supply_get_drvdata(psy);
int charge_now_mAh, charge_full_mAh, ret;
guard(mutex)(&data->update_lock);
ret = atla10_ec_update(data);
if (ret)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (data->state.status & ACPI_BATTERY_STATE_DISCHARGING)
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (data->state.status & ACPI_BATTERY_STATE_CHARGING)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (data->state.capacity == 100)
val->intval = POWER_SUPPLY_STATUS_FULL;
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = data->state.capacity;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
/*
* The EC has a bug where it reports charge-full-design as
* charge-now when the battery is full. Clamp charge-now to
* charge-full to workaround this.
*/
charge_now_mAh = le16_to_cpu(data->state.charge_now_mAh);
charge_full_mAh = le16_to_cpu(data->state.charge_full_mAh);
val->intval = min(charge_now_mAh, charge_full_mAh) * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = le16_to_cpu(data->state.voltage_now_mV) * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = le16_to_cpu(data->state.current_now_mA) * 1000;
/*
* Documentation/ABI/testing/sysfs-class-power specifies
* negative current for discharging.
*/
if (data->state.status & ACPI_BATTERY_STATE_DISCHARGING)
val->intval = -val->intval;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
val->intval = le16_to_cpu(data->state.charge_full_mAh) * 1000;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = le16_to_cpu(data->state.temp) / 10;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = le16_to_cpu(data->info.charge_full_design_mAh) * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = ATLA10_EC_VOLTAGE_MIN_DESIGN_uV;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO;
break;
default:
return -EINVAL;
}
return 0;
}
static void atla10_ec_external_power_changed_work(struct work_struct *work)
{
struct atla10_ec_data *data = container_of(work, struct atla10_ec_data, work.work);
dev_dbg(&data->client->dev, "External power changed\n");
data->valid = false;
power_supply_changed(data->psy);
}
static void atla10_ec_external_power_changed(struct power_supply *psy)
{
struct atla10_ec_data *data = power_supply_get_drvdata(psy);
/* After charger plug in/out wait 0.5s for things to stabilize */
mod_delayed_work(system_wq, &data->work, HZ / 2);
}
static const enum power_supply_property atla10_ec_psy_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
};
static const struct power_supply_desc atla10_ec_psy_desc = {
.name = "atla10_ec_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = atla10_ec_psy_props,
.num_properties = ARRAY_SIZE(atla10_ec_psy_props),
.get_property = atla10_ec_psy_get_property,
.external_power_changed = atla10_ec_external_power_changed,
};
static int atla10_ec_probe(struct i2c_client *client)
{
struct power_supply_config psy_cfg = { };
struct device *dev = &client->dev;
struct atla10_ec_data *data;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
psy_cfg.drv_data = data;
data->client = client;
ret = devm_mutex_init(dev, &data->update_lock);
if (ret)
return ret;
ret = devm_delayed_work_autocancel(dev, &data->work,
atla10_ec_external_power_changed_work);
if (ret)
return ret;
ret = atla10_ec_cmd(data, ATLA10_EC_BATTERY_INFO_COMMAND,
sizeof(data->info), (u8 *)&data->info);
if (ret)
return ret;
data->psy = devm_power_supply_register(dev, &atla10_ec_psy_desc, &psy_cfg);
return PTR_ERR_OR_ZERO(data->psy);
}
static const struct i2c_device_id atla10_ec_id_table[] = {
{ "vexia_atla10_ec" },
{ }
};
MODULE_DEVICE_TABLE(i2c, atla10_ec_id_table);
static struct i2c_driver atla10_ec_driver = {
.driver = {
.name = "vexia_atla10_ec",
},
.probe = atla10_ec_probe,
.id_table = atla10_ec_id_table,
};
module_i2c_driver(atla10_ec_driver);
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_DESCRIPTION("Battery driver for Vexia EDU ATLA 10 tablet EC");
MODULE_LICENSE("GPL");

View File

@ -57,8 +57,15 @@ struct x86_spi_dev_info {
};
struct x86_serdev_info {
const char *ctrl_hid;
const char *ctrl_uid;
union {
struct {
const char *hid;
const char *uid;
} acpi;
struct {
unsigned int devfn;
} pci;
} ctrl;
const char *ctrl_devname;
/*
* ATM the serdev core only supports of or ACPI matching; and so far all
@ -91,7 +98,7 @@ struct x86_dev_info {
int gpio_button_count;
int (*init)(struct device *dev);
void (*exit)(void);
bool use_pci_devname;
bool use_pci;
};
int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,

View File

@ -23,10 +23,15 @@ enum platform_profile_option {
PLATFORM_PROFILE_BALANCED,
PLATFORM_PROFILE_BALANCED_PERFORMANCE,
PLATFORM_PROFILE_PERFORMANCE,
PLATFORM_PROFILE_CUSTOM,
PLATFORM_PROFILE_LAST, /*must always be last */
};
struct platform_profile_handler {
const char *name;
struct device *dev;
struct device *class_dev;
int minor;
unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
int (*profile_get)(struct platform_profile_handler *pprof,
enum platform_profile_option *profile);
@ -35,8 +40,8 @@ struct platform_profile_handler {
};
int platform_profile_register(struct platform_profile_handler *pprof);
int platform_profile_remove(void);
int platform_profile_remove(struct platform_profile_handler *pprof);
int platform_profile_cycle(void);
void platform_profile_notify(void);
void platform_profile_notify(struct platform_profile_handler *pprof);
#endif /*_PLATFORM_PROFILE_H_*/