Unverified Commit 18131e9f authored by Ilpo Järvinen's avatar Ilpo Järvinen
Browse files

Merge branch 'platform-drivers-x86-platform-profile' into for-next

parents 3b6f9c65 0056b085
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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.
+38 −0
Original line number Diff line number Diff line
@@ -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.
+430 −104
Original line number Diff line number Diff line
@@ -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,
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 i, len = 0;

	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");

	return len;
}

/**
 * _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)
{
	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;

	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 (WARN_ON(val >= PLATFORM_PROFILE_LAST))
		return -EINVAL;
	*profile = val;

	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)
{
	int len = 0;
	int err, i;
	struct platform_profile_handler *handler = dev_get_drvdata(dev);

	err = mutex_lock_interruptible(&profile_lock);
	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;
	}

	if (!cur_profile) {
		mutex_unlock(&profile_lock);
		return -ENODEV;
	return sysfs_emit(buf, "%s\n", profile_names[profile]);
}

	for_each_set_bit(i, cur_profile->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]);
/**
 * 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 index, ret;

	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;
	}
	len += sysfs_emit_at(buf, len, "\n");
	mutex_unlock(&profile_lock);
	return len;

	sysfs_notify(acpi_kobj, NULL, "platform_profile");

	return count;
}
static DEVICE_ATTR_RW(profile);

static ssize_t platform_profile_show(struct device *dev,
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)
{
	enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED;
	unsigned long aggregate[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
	int err;

	err = mutex_lock_interruptible(&profile_lock);
	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;
	}

	if (!cur_profile) {
		mutex_unlock(&profile_lock);
		return -ENODEV;
	/* no profile handler registered any more */
	if (bitmap_empty(aggregate, PLATFORM_PROFILE_LAST))
		return -EINVAL;

	return _commmon_choices_show(aggregate, buf);
}

	err = cur_profile->profile_get(cur_profile, &profile);
	mutex_unlock(&profile_lock);
/**
 * _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;

	/* Check that profile is valid index */
	if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names))))
		return -EIO;
	if (*profile != PLATFORM_PROFILE_LAST && *profile != val)
		*profile = PLATFORM_PROFILE_CUSTOM;
	else
		*profile = val;

	return sysfs_emit(buf, "%s\n", profile_names[profile]);
	return 0;
}

static ssize_t platform_profile_store(struct device *dev,
/**
 * _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,
			    const char *buf, size_t count)
				     char *buf)
{
	int err, i;
	enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
	int err;

	err = mutex_lock_interruptible(&profile_lock);
	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;
	/* 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;
	}

	/* Check that platform supports this profile choice */
	if (!test_bit(i, cur_profile->choices)) {
		mutex_unlock(&profile_lock);
	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;
	}

	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);
	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 = cur_profile->profile_get(cur_profile, &profile);
	if (err) {
		mutex_unlock(&profile_lock);
		err = class_for_each_device(&platform_profile_class, NULL,
					    choices, _aggregate_choices);
		if (err)
			return err;
	}

	next = find_next_bit_wrap(cur_profile->choices, PLATFORM_PROFILE_LAST,
		/* 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);

	if (WARN_ON(next == PLATFORM_PROFILE_LAST)) {
		mutex_unlock(&profile_lock);
		return -EINVAL;
	}
		err = class_for_each_device(&platform_profile_class, NULL, &next,
					    _store_and_notify);

	err = cur_profile->profile_set(cur_profile, next);
	mutex_unlock(&profile_lock);
		if (err)
			return err;
	}

	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);
		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");
+7 −1
Original line number Diff line number Diff line
@@ -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[] = {
+7 −5
Original line number Diff line number Diff line
@@ -1900,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 =
@@ -2010,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;
@@ -2554,7 +2556,7 @@ 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;
	}
@@ -2569,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:
@@ -2592,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
Loading