#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/component.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/gfp_types.h>
#include <linux/hwmon.h>
#include <linux/idr.h>
#include <linux/kdev_t.h>
#include <linux/kobject.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_profile.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "wmi-capdata.h"
#include "wmi-events.h"
#include "wmi-gamezone.h"
#include "wmi-helpers.h"
#include "wmi-other.h"
#include "../firmware_attributes_class.h"
#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
#define LWMI_DEVICE_ID_CPU 0x01
#define LWMI_FEATURE_ID_CPU_SPPT 0x01
#define LWMI_FEATURE_ID_CPU_SPL 0x02
#define LWMI_FEATURE_ID_CPU_FPPT 0x03
#define LWMI_FEATURE_ID_FAN_RPM 0x03
#define LWMI_TYPE_ID_NONE 0x00
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
#define LWMI_FAN_ID_BASE 1
#define LWMI_FAN_NR 4
#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE)
#define LWMI_ATTR_ID_FAN_RPM(x) \
(FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x)))
#define LWMI_FAN_DIV 100
#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
static DEFINE_IDA(lwmi_om_ida);
enum attribute_property {
DEFAULT_VAL,
MAX_VAL,
MIN_VAL,
STEP_VAL,
SUPPORTED,
};
struct lwmi_fan_info {
u32 supported;
u32 last_target;
long min_rpm;
long max_rpm;
};
struct lwmi_om_priv {
struct component_master_ops *ops;
struct cd_list *cd00_list;
struct cd_list *cd01_list;
struct device *hwmon_dev;
struct device *fw_attr_dev;
struct kset *fw_attr_kset;
struct notifier_block nb;
struct wmi_device *wdev;
int ida_id;
struct lwmi_fan_info fan_info[LWMI_FAN_NR];
struct {
bool capdata00_collected : 1;
bool capdata_fan_collected : 1;
} fan_flags;
};
static bool expose_all_fans;
module_param(expose_all_fans, bool, 0444);
MODULE_PARM_DESC(expose_all_fans,
"This option skips some capability checks and solely relies on per-channel ones "
"to expose fan attributes. Use with caution.");
static bool relax_fan_constraint;
module_param(relax_fan_constraint, bool, 0444);
MODULE_PARM_DESC(relax_fan_constraint,
"Do not enforce fan RPM constraint (div/min/max) "
"and enables fan tuning when such data is missing. "
"Enabling this may results in HWMON attributes being out-of-sync, "
"and setting a too low RPM stops the fan. Use with caution.");
static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set)
{
struct wmi_method_args_32 args;
u32 method_id, retval;
int err;
method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET;
args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel);
args.arg1 = set ? *val : 0;
err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id,
(unsigned char *)&args, sizeof(args), &retval);
if (err)
return err;
if (!set) {
*val = retval;
return 0;
}
return (retval == 0 || retval == 1) ? 0 : -EIO;
}
static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
u32 attr, int channel)
{
struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata;
bool visible = false;
if (type == hwmon_fan) {
if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID))
return 0;
switch (attr) {
case hwmon_fan_target:
if (!(priv->fan_info[channel].supported & LWMI_SUPP_SET))
return 0;
if (relax_fan_constraint ||
(priv->fan_info[channel].min_rpm >= 0 &&
priv->fan_info[channel].max_rpm >= 0))
return 0644;
dev_warn_once(&priv->wdev->dev,
"fan tuning disabled due to missing RPM constraint\n");
return 0;
case hwmon_fan_div:
case hwmon_fan_input:
visible = priv->fan_info[channel].supported & LWMI_SUPP_GET;
break;
case hwmon_fan_min:
visible = priv->fan_info[channel].min_rpm >= 0;
break;
case hwmon_fan_max:
visible = priv->fan_info[channel].max_rpm >= 0;
break;
}
}
return visible ? 0444 : 0;
}
static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
u32 retval = 0;
int err;
if (type == hwmon_fan) {
switch (attr) {
case hwmon_fan_div:
*val = LWMI_FAN_DIV;
return 0;
case hwmon_fan_input:
err = lwmi_om_fan_get_set(priv, channel, &retval, false);
if (err)
return err;
*val = retval;
return 0;
case hwmon_fan_target:
*val = priv->fan_info[channel].last_target;
return 0;
case hwmon_fan_min:
*val = priv->fan_info[channel].min_rpm;
return 0;
case hwmon_fan_max:
*val = priv->fan_info[channel].max_rpm;
return 0;
}
}
return -EOPNOTSUPP;
}
static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
u32 raw, min_rpm, max_rpm;
int err;
if (type == hwmon_fan) {
switch (attr) {
case hwmon_fan_target:
if (relax_fan_constraint) {
min_rpm = 1;
max_rpm = U16_MAX;
} else {
min_rpm = priv->fan_info[channel].min_rpm;
max_rpm = priv->fan_info[channel].max_rpm;
}
if (val != 0 && (val < min_rpm || val > max_rpm))
return -EINVAL;
if (!relax_fan_constraint)
raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV;
err = lwmi_om_fan_get_set(priv, channel, &raw, true);
if (err)
return err;
priv->fan_info[channel].last_target = raw;
return 0;
}
}
return -EOPNOTSUPP;
}
static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
HWMON_F_MIN | HWMON_F_MAX,
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
HWMON_F_MIN | HWMON_F_MAX,
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
HWMON_F_MIN | HWMON_F_MAX,
HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
HWMON_F_MIN | HWMON_F_MAX),
NULL
};
static const struct hwmon_ops lwmi_om_hwmon_ops = {
.is_visible = lwmi_om_hwmon_is_visible,
.read = lwmi_om_hwmon_read,
.write = lwmi_om_hwmon_write,
};
static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
.ops = &lwmi_om_hwmon_ops,
.info = lwmi_om_hwmon_info,
};
static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
{
int i, valid;
if (WARN_ON(priv->hwmon_dev))
return;
if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) {
dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n",
priv->fan_flags.capdata00_collected,
priv->fan_flags.capdata_fan_collected);
return;
}
if (expose_all_fans)
dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n");
if (relax_fan_constraint)
dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n");
valid = 0;
for (i = 0; i < LWMI_FAN_NR; i++) {
if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID))
continue;
valid++;
if (!expose_all_fans &&
(priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) {
dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n",
LWMI_FAN_ID(i));
priv->fan_info[i].supported = 0;
valid--;
}
}
if (valid == 0) {
dev_warn(&priv->wdev->dev,
"fan reporting/tuning is unsupported on this device\n");
return;
}
priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev,
LWMI_OM_HWMON_NAME, priv,
&lwmi_om_hwmon_chip_info,
NULL);
if (IS_ERR(priv->hwmon_dev)) {
dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n",
PTR_ERR(priv->hwmon_dev));
priv->hwmon_dev = NULL;
return;
}
dev_dbg(&priv->wdev->dev, "registered HWMON device\n");
}
static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
{
if (!priv->hwmon_dev)
return;
hwmon_device_unregister(priv->hwmon_dev);
priv->hwmon_dev = NULL;
}
static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv)
{
int i;
for (i = 0; i < LWMI_FAN_NR; i++) {
priv->fan_info[i] = (struct lwmi_fan_info) {
.supported = 0,
.last_target = 0,
.min_rpm = -ENODATA,
.max_rpm = -ENODATA,
};
}
priv->fan_flags.capdata00_collected = false;
priv->fan_flags.capdata_fan_collected = false;
}
static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv)
{
struct capdata00 capdata00;
int i, err;
dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n");
for (i = 0; i < LWMI_FAN_NR; i++) {
err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00);
priv->fan_info[i].supported = err ? 0 : capdata00.supported;
}
priv->fan_flags.capdata00_collected = true;
lwmi_om_hwmon_add(priv);
}
static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
struct capdata_fan capdata_fan;
int i, err;
dev_dbg(dev, "Collecting fan info from capdata_fan\n");
if (!cd_fan_list)
goto out;
for (i = 0; i < LWMI_FAN_NR; i++) {
err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan);
if (err)
continue;
priv->fan_info[i].min_rpm = capdata_fan.min_rpm;
priv->fan_info[i].max_rpm = capdata_fan.max_rpm;
}
out:
priv->fan_flags.capdata_fan_collected = true;
lwmi_om_hwmon_add(priv);
}
struct tunable_attr_01 {
struct capdata01 *capdata;
struct device *dev;
u32 feature_id;
u32 device_id;
u32 type_id;
};
static struct tunable_attr_01 ppt_pl1_spl = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_SPL,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl2_sppt = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_SPPT,
.type_id = LWMI_TYPE_ID_NONE,
};
static struct tunable_attr_01 ppt_pl3_fppt = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_FPPT,
.type_id = LWMI_TYPE_ID_NONE,
};
struct capdata01_attr_group {
const struct attribute_group *attr_group;
struct tunable_attr_01 *tunable_attr;
};
int lwmi_om_register_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&om_chain_head, nb);
}
EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
int lwmi_om_unregister_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&om_chain_head, nb);
}
EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
static void devm_lwmi_om_unregister_notifier(void *data)
{
struct notifier_block *nb = data;
lwmi_om_unregister_notifier(nb);
}
int devm_lwmi_om_register_notifier(struct device *dev,
struct notifier_block *nb)
{
int ret;
ret = lwmi_om_register_notifier(nb);
if (ret < 0)
return ret;
return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
nb);
}
EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
static int lwmi_om_notifier_call(enum thermal_mode *mode)
{
int ret;
ret = blocking_notifier_call_chain(&om_chain_head,
LWMI_GZ_GET_THERMAL_MODE, &mode);
if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK)
return -EINVAL;
return 0;
}
static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
char *buf)
{
return sysfs_emit(buf, "integer\n");
}
static ssize_t attr_capdata01_show(struct kobject *kobj,
struct kobj_attribute *kattr, char *buf,
struct tunable_attr_01 *tunable_attr,
enum attribute_property prop)
{
struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
struct capdata01 capdata;
u32 attribute_id;
int value, ret;
attribute_id =
FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
LWMI_GZ_THERMAL_MODE_CUSTOM) |
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
if (ret)
return ret;
switch (prop) {
case DEFAULT_VAL:
value = capdata.default_value;
break;
case MAX_VAL:
value = capdata.max_value;
break;
case MIN_VAL:
value = capdata.min_value;
break;
case STEP_VAL:
value = capdata.step;
break;
default:
return -EINVAL;
}
return sysfs_emit(buf, "%d\n", value);
}
static ssize_t attr_current_value_store(struct kobject *kobj,
struct kobj_attribute *kattr,
const char *buf, size_t count,
struct tunable_attr_01 *tunable_attr)
{
struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
struct wmi_method_args_32 args;
struct capdata01 capdata;
enum thermal_mode mode;
u32 attribute_id;
u32 value;
int ret;
ret = lwmi_om_notifier_call(&mode);
if (ret)
return ret;
if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
return -EBUSY;
attribute_id =
FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
if (ret)
return ret;
ret = kstrtouint(buf, 10, &value);
if (ret)
return ret;
if (value < capdata.min_value || value > capdata.max_value)
return -EINVAL;
args.arg0 = attribute_id;
args.arg1 = value;
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
(unsigned char *)&args, sizeof(args), NULL);
if (ret)
return ret;
return count;
};
static ssize_t attr_current_value_show(struct kobject *kobj,
struct kobj_attribute *kattr, char *buf,
struct tunable_attr_01 *tunable_attr)
{
struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
struct wmi_method_args_32 args;
enum thermal_mode mode;
u32 attribute_id;
int retval;
int ret;
ret = lwmi_om_notifier_call(&mode);
if (ret)
return ret;
attribute_id =
FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
args.arg0 = attribute_id;
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
(unsigned char *)&args, sizeof(args),
&retval);
if (ret)
return ret;
return sysfs_emit(buf, "%d\n", retval);
}
#define __LWMI_ATTR_RO(_func, _name) \
{ \
.attr = { .name = __stringify(_name), .mode = 0444 }, \
.show = _func##_##_name##_show, \
}
#define __LWMI_ATTR_RO_AS(_name, _show) \
{ \
.attr = { .name = __stringify(_name), .mode = 0444 }, \
.show = _show, \
}
#define __LWMI_ATTR_RW(_func, _name) \
__ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
#define __LWMI_ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \
static ssize_t _attrname##_##_prop##_show( \
struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
{ \
return sysfs_emit(buf, _fmt, _val); \
} \
static struct kobj_attribute attr_##_attrname##_##_prop = \
__LWMI_ATTR_RO(_attrname, _prop)
#define __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
static ssize_t _attrname##_current_value_store( \
struct kobject *kobj, struct kobj_attribute *kattr, \
const char *buf, size_t count) \
{ \
return attr_current_value_store(kobj, kattr, buf, count, \
&_attrname); \
} \
static ssize_t _attrname##_current_value_show( \
struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
{ \
return attr_current_value_show(kobj, kattr, buf, &_attrname); \
} \
static struct kobj_attribute attr_##_attrname##_current_value = \
__LWMI_ATTR_RW(_attrname, current_value)
#define __LWMI_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
static ssize_t _attrname##_##_prop##_show( \
struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
{ \
return attr_capdata01_show(kobj, kattr, buf, &_attrname, \
_prop_type); \
} \
static struct kobj_attribute attr_##_attrname##_##_prop = \
__LWMI_ATTR_RO(_attrname, _prop)
#define LWMI_ATTR_GROUP_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
__LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname); \
__LWMI_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
__LWMI_ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
__LWMI_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
__LWMI_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
__LWMI_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL); \
static struct kobj_attribute attr_##_attrname##_type = \
__LWMI_ATTR_RO_AS(type, int_type_show); \
static struct attribute *_attrname##_attrs[] = { \
&attr_##_attrname##_current_value.attr, \
&attr_##_attrname##_default_value.attr, \
&attr_##_attrname##_display_name.attr, \
&attr_##_attrname##_max_value.attr, \
&attr_##_attrname##_min_value.attr, \
&attr_##_attrname##_scalar_increment.attr, \
&attr_##_attrname##_type.attr, \
NULL, \
}; \
static const struct attribute_group _attrname##_attr_group = { \
.name = _fsname, .attrs = _attrname##_attrs \
}
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
"Set the CPU sustained power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
"Set the CPU slow package power tracking limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
"Set the CPU fast package power tracking limit");
static struct capdata01_attr_group cd01_attr_groups[] = {
{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
{},
};
static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
{
unsigned int i;
int err;
priv->ida_id = ida_alloc(&lwmi_om_ida, GFP_KERNEL);
if (priv->ida_id < 0)
return priv->ida_id;
priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
MKDEV(0, 0), NULL, "%s-%u",
LWMI_OM_FW_ATTR_BASE_PATH,
priv->ida_id);
if (IS_ERR(priv->fw_attr_dev)) {
err = PTR_ERR(priv->fw_attr_dev);
goto err_free_ida;
}
priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
&priv->fw_attr_dev->kobj);
if (!priv->fw_attr_kset) {
err = -ENOMEM;
goto err_destroy_classdev;
}
for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
err = sysfs_create_group(&priv->fw_attr_kset->kobj,
cd01_attr_groups[i].attr_group);
if (err)
goto err_remove_groups;
cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
}
return 0;
err_remove_groups:
while (i--)
sysfs_remove_group(&priv->fw_attr_kset->kobj,
cd01_attr_groups[i].attr_group);
kset_unregister(priv->fw_attr_kset);
err_destroy_classdev:
device_unregister(priv->fw_attr_dev);
err_free_ida:
ida_free(&lwmi_om_ida, priv->ida_id);
return err;
}
static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
{
for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++)
sysfs_remove_group(&priv->fw_attr_kset->kobj,
cd01_attr_groups[i].attr_group);
kset_unregister(priv->fw_attr_kset);
device_unregister(priv->fw_attr_dev);
}
static int lwmi_om_master_bind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
struct lwmi_cd_binder binder = {
.cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan,
};
int ret;
lwmi_om_fan_info_init(priv);
ret = component_bind_all(dev, &binder);
if (ret)
return ret;
priv->cd00_list = binder.cd00_list;
priv->cd01_list = binder.cd01_list;
if (!priv->cd00_list || !priv->cd01_list)
return -ENODEV;
lwmi_om_fan_info_collect_cd00(priv);
return lwmi_om_fw_attr_add(priv);
}
static void lwmi_om_master_unbind(struct device *dev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(dev);
lwmi_om_fw_attr_remove(priv);
lwmi_om_hwmon_remove(priv);
component_unbind_all(dev, NULL);
}
static const struct component_master_ops lwmi_om_master_ops = {
.bind = lwmi_om_master_bind,
.unbind = lwmi_om_master_unbind,
};
static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
{
struct component_match *master_match = NULL;
struct lwmi_om_priv *priv;
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->ida_id = -EIDRM;
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
lwmi_cd_match_add_all(&wdev->dev, &master_match);
if (IS_ERR(master_match))
return PTR_ERR(master_match);
return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
master_match);
}
static void lwmi_other_remove(struct wmi_device *wdev)
{
struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
component_master_del(&wdev->dev, &lwmi_om_master_ops);
if (priv->ida_id >= 0)
ida_free(&lwmi_om_ida, priv->ida_id);
}
static const struct wmi_device_id lwmi_other_id_table[] = {
{ LENOVO_OTHER_MODE_GUID, NULL },
{}
};
static struct wmi_driver lwmi_other_driver = {
.driver = {
.name = "lenovo_wmi_other",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = lwmi_other_id_table,
.probe = lwmi_other_probe,
.remove = lwmi_other_remove,
.no_singleton = true,
};
module_wmi_driver(lwmi_other_driver);
MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA");
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
MODULE_LICENSE("GPL");