1*e1a5fe66SDerek J. Clark // SPDX-License-Identifier: GPL-2.0-or-later 2*e1a5fe66SDerek J. Clark /* 3*e1a5fe66SDerek J. Clark * Lenovo Capability Data 01 WMI Data Block driver. 4*e1a5fe66SDerek J. Clark * 5*e1a5fe66SDerek J. Clark * Lenovo Capability Data 01 provides information on tunable attributes used by 6*e1a5fe66SDerek J. Clark * the "Other Mode" WMI interface. The data includes if the attribute is 7*e1a5fe66SDerek J. Clark * supported by the hardware, the default_value, max_value, min_value, and step 8*e1a5fe66SDerek J. Clark * increment. Each attribute has multiple pages, one for each of the thermal 9*e1a5fe66SDerek J. Clark * modes managed by the Gamezone interface. 10*e1a5fe66SDerek J. Clark * 11*e1a5fe66SDerek J. Clark * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> 12*e1a5fe66SDerek J. Clark */ 13*e1a5fe66SDerek J. Clark 14*e1a5fe66SDerek J. Clark #include <linux/acpi.h> 15*e1a5fe66SDerek J. Clark #include <linux/cleanup.h> 16*e1a5fe66SDerek J. Clark #include <linux/component.h> 17*e1a5fe66SDerek J. Clark #include <linux/container_of.h> 18*e1a5fe66SDerek J. Clark #include <linux/device.h> 19*e1a5fe66SDerek J. Clark #include <linux/export.h> 20*e1a5fe66SDerek J. Clark #include <linux/gfp_types.h> 21*e1a5fe66SDerek J. Clark #include <linux/module.h> 22*e1a5fe66SDerek J. Clark #include <linux/mutex.h> 23*e1a5fe66SDerek J. Clark #include <linux/mutex_types.h> 24*e1a5fe66SDerek J. Clark #include <linux/notifier.h> 25*e1a5fe66SDerek J. Clark #include <linux/overflow.h> 26*e1a5fe66SDerek J. Clark #include <linux/types.h> 27*e1a5fe66SDerek J. Clark #include <linux/wmi.h> 28*e1a5fe66SDerek J. Clark 29*e1a5fe66SDerek J. Clark #include "wmi-capdata01.h" 30*e1a5fe66SDerek J. Clark 31*e1a5fe66SDerek J. Clark #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" 32*e1a5fe66SDerek J. Clark 33*e1a5fe66SDerek J. Clark #define ACPI_AC_CLASS "ac_adapter" 34*e1a5fe66SDerek J. Clark #define ACPI_AC_NOTIFY_STATUS 0x80 35*e1a5fe66SDerek J. Clark 36*e1a5fe66SDerek J. Clark struct lwmi_cd01_priv { 37*e1a5fe66SDerek J. Clark struct notifier_block acpi_nb; /* ACPI events */ 38*e1a5fe66SDerek J. Clark struct wmi_device *wdev; 39*e1a5fe66SDerek J. Clark struct cd01_list *list; 40*e1a5fe66SDerek J. Clark }; 41*e1a5fe66SDerek J. Clark 42*e1a5fe66SDerek J. Clark struct cd01_list { 43*e1a5fe66SDerek J. Clark struct mutex list_mutex; /* list R/W mutex */ 44*e1a5fe66SDerek J. Clark u8 count; 45*e1a5fe66SDerek J. Clark struct capdata01 data[]; 46*e1a5fe66SDerek J. Clark }; 47*e1a5fe66SDerek J. Clark 48*e1a5fe66SDerek J. Clark /** 49*e1a5fe66SDerek J. Clark * lwmi_cd01_component_bind() - Bind component to master device. 50*e1a5fe66SDerek J. Clark * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device. 51*e1a5fe66SDerek J. Clark * @om_dev: Pointer to the lenovo-wmi-other driver parent device. 52*e1a5fe66SDerek J. Clark * @data: capdata01_list object pointer used to return the capability data. 53*e1a5fe66SDerek J. Clark * 54*e1a5fe66SDerek J. Clark * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01 55*e1a5fe66SDerek J. Clark * list. This is used to call lwmi_cd01_get_data to look up attribute data 56*e1a5fe66SDerek J. Clark * from the lenovo-wmi-other driver. 57*e1a5fe66SDerek J. Clark * 58*e1a5fe66SDerek J. Clark * Return: 0 59*e1a5fe66SDerek J. Clark */ 60*e1a5fe66SDerek J. Clark static int lwmi_cd01_component_bind(struct device *cd01_dev, 61*e1a5fe66SDerek J. Clark struct device *om_dev, void *data) 62*e1a5fe66SDerek J. Clark { 63*e1a5fe66SDerek J. Clark struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev); 64*e1a5fe66SDerek J. Clark struct cd01_list **cd01_list = data; 65*e1a5fe66SDerek J. Clark 66*e1a5fe66SDerek J. Clark *cd01_list = priv->list; 67*e1a5fe66SDerek J. Clark 68*e1a5fe66SDerek J. Clark return 0; 69*e1a5fe66SDerek J. Clark } 70*e1a5fe66SDerek J. Clark 71*e1a5fe66SDerek J. Clark static const struct component_ops lwmi_cd01_component_ops = { 72*e1a5fe66SDerek J. Clark .bind = lwmi_cd01_component_bind, 73*e1a5fe66SDerek J. Clark }; 74*e1a5fe66SDerek J. Clark 75*e1a5fe66SDerek J. Clark /** 76*e1a5fe66SDerek J. Clark * lwmi_cd01_get_data - Get the data of the specified attribute 77*e1a5fe66SDerek J. Clark * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct. 78*e1a5fe66SDerek J. Clark * @attribute_id: The capdata attribute ID to be found. 79*e1a5fe66SDerek J. Clark * @output: Pointer to a capdata01 struct to return the data. 80*e1a5fe66SDerek J. Clark * 81*e1a5fe66SDerek J. Clark * Retrieves the capability data 01 struct pointer for the given 82*e1a5fe66SDerek J. Clark * attribute for its specified thermal mode. 83*e1a5fe66SDerek J. Clark * 84*e1a5fe66SDerek J. Clark * Return: 0 on success, or -EINVAL. 85*e1a5fe66SDerek J. Clark */ 86*e1a5fe66SDerek J. Clark int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output) 87*e1a5fe66SDerek J. Clark { 88*e1a5fe66SDerek J. Clark u8 idx; 89*e1a5fe66SDerek J. Clark 90*e1a5fe66SDerek J. Clark guard(mutex)(&list->list_mutex); 91*e1a5fe66SDerek J. Clark for (idx = 0; idx < list->count; idx++) { 92*e1a5fe66SDerek J. Clark if (list->data[idx].id != attribute_id) 93*e1a5fe66SDerek J. Clark continue; 94*e1a5fe66SDerek J. Clark memcpy(output, &list->data[idx], sizeof(list->data[idx])); 95*e1a5fe66SDerek J. Clark return 0; 96*e1a5fe66SDerek J. Clark }; 97*e1a5fe66SDerek J. Clark 98*e1a5fe66SDerek J. Clark return -EINVAL; 99*e1a5fe66SDerek J. Clark } 100*e1a5fe66SDerek J. Clark EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01"); 101*e1a5fe66SDerek J. Clark 102*e1a5fe66SDerek J. Clark /** 103*e1a5fe66SDerek J. Clark * lwmi_cd01_cache() - Cache all WMI data block information 104*e1a5fe66SDerek J. Clark * @priv: lenovo-wmi-capdata01 driver data. 105*e1a5fe66SDerek J. Clark * 106*e1a5fe66SDerek J. Clark * Loop through each WMI data block and cache the data. 107*e1a5fe66SDerek J. Clark * 108*e1a5fe66SDerek J. Clark * Return: 0 on success, or an error. 109*e1a5fe66SDerek J. Clark */ 110*e1a5fe66SDerek J. Clark static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv) 111*e1a5fe66SDerek J. Clark { 112*e1a5fe66SDerek J. Clark int idx; 113*e1a5fe66SDerek J. Clark 114*e1a5fe66SDerek J. Clark guard(mutex)(&priv->list->list_mutex); 115*e1a5fe66SDerek J. Clark for (idx = 0; idx < priv->list->count; idx++) { 116*e1a5fe66SDerek J. Clark union acpi_object *ret_obj __free(kfree) = NULL; 117*e1a5fe66SDerek J. Clark 118*e1a5fe66SDerek J. Clark ret_obj = wmidev_block_query(priv->wdev, idx); 119*e1a5fe66SDerek J. Clark if (!ret_obj) 120*e1a5fe66SDerek J. Clark return -ENODEV; 121*e1a5fe66SDerek J. Clark 122*e1a5fe66SDerek J. Clark if (ret_obj->type != ACPI_TYPE_BUFFER || 123*e1a5fe66SDerek J. Clark ret_obj->buffer.length < sizeof(priv->list->data[idx])) 124*e1a5fe66SDerek J. Clark continue; 125*e1a5fe66SDerek J. Clark 126*e1a5fe66SDerek J. Clark memcpy(&priv->list->data[idx], ret_obj->buffer.pointer, 127*e1a5fe66SDerek J. Clark ret_obj->buffer.length); 128*e1a5fe66SDerek J. Clark } 129*e1a5fe66SDerek J. Clark 130*e1a5fe66SDerek J. Clark return 0; 131*e1a5fe66SDerek J. Clark } 132*e1a5fe66SDerek J. Clark 133*e1a5fe66SDerek J. Clark /** 134*e1a5fe66SDerek J. Clark * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata 135*e1a5fe66SDerek J. Clark * @priv: lenovo-wmi-capdata01 driver data. 136*e1a5fe66SDerek J. Clark * 137*e1a5fe66SDerek J. Clark * Allocate a cd01_list struct large enough to contain data from all WMI data 138*e1a5fe66SDerek J. Clark * blocks provided by the interface. 139*e1a5fe66SDerek J. Clark * 140*e1a5fe66SDerek J. Clark * Return: 0 on success, or an error. 141*e1a5fe66SDerek J. Clark */ 142*e1a5fe66SDerek J. Clark static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv) 143*e1a5fe66SDerek J. Clark { 144*e1a5fe66SDerek J. Clark struct cd01_list *list; 145*e1a5fe66SDerek J. Clark size_t list_size; 146*e1a5fe66SDerek J. Clark int count, ret; 147*e1a5fe66SDerek J. Clark 148*e1a5fe66SDerek J. Clark count = wmidev_instance_count(priv->wdev); 149*e1a5fe66SDerek J. Clark list_size = struct_size(list, data, count); 150*e1a5fe66SDerek J. Clark 151*e1a5fe66SDerek J. Clark list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL); 152*e1a5fe66SDerek J. Clark if (!list) 153*e1a5fe66SDerek J. Clark return -ENOMEM; 154*e1a5fe66SDerek J. Clark 155*e1a5fe66SDerek J. Clark ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex); 156*e1a5fe66SDerek J. Clark if (ret) 157*e1a5fe66SDerek J. Clark return ret; 158*e1a5fe66SDerek J. Clark 159*e1a5fe66SDerek J. Clark list->count = count; 160*e1a5fe66SDerek J. Clark priv->list = list; 161*e1a5fe66SDerek J. Clark 162*e1a5fe66SDerek J. Clark return 0; 163*e1a5fe66SDerek J. Clark } 164*e1a5fe66SDerek J. Clark 165*e1a5fe66SDerek J. Clark /** 166*e1a5fe66SDerek J. Clark * lwmi_cd01_setup() - Cache all WMI data block information 167*e1a5fe66SDerek J. Clark * @priv: lenovo-wmi-capdata01 driver data. 168*e1a5fe66SDerek J. Clark * 169*e1a5fe66SDerek J. Clark * Allocate a cd01_list struct large enough to contain data from all WMI data 170*e1a5fe66SDerek J. Clark * blocks provided by the interface. Then loop through each data block and 171*e1a5fe66SDerek J. Clark * cache the data. 172*e1a5fe66SDerek J. Clark * 173*e1a5fe66SDerek J. Clark * Return: 0 on success, or an error code. 174*e1a5fe66SDerek J. Clark */ 175*e1a5fe66SDerek J. Clark static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv) 176*e1a5fe66SDerek J. Clark { 177*e1a5fe66SDerek J. Clark int ret; 178*e1a5fe66SDerek J. Clark 179*e1a5fe66SDerek J. Clark ret = lwmi_cd01_alloc(priv); 180*e1a5fe66SDerek J. Clark if (ret) 181*e1a5fe66SDerek J. Clark return ret; 182*e1a5fe66SDerek J. Clark 183*e1a5fe66SDerek J. Clark return lwmi_cd01_cache(priv); 184*e1a5fe66SDerek J. Clark } 185*e1a5fe66SDerek J. Clark 186*e1a5fe66SDerek J. Clark /** 187*e1a5fe66SDerek J. Clark * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier. 188*e1a5fe66SDerek J. Clark * block call chain. 189*e1a5fe66SDerek J. Clark * @nb: The notifier_block registered to lenovo-wmi-events driver. 190*e1a5fe66SDerek J. Clark * @action: Unused. 191*e1a5fe66SDerek J. Clark * @data: The ACPI event. 192*e1a5fe66SDerek J. Clark * 193*e1a5fe66SDerek J. Clark * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile 194*e1a5fe66SDerek J. Clark * of a change. 195*e1a5fe66SDerek J. Clark * 196*e1a5fe66SDerek J. Clark * Return: notifier_block status. 197*e1a5fe66SDerek J. Clark */ 198*e1a5fe66SDerek J. Clark static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action, 199*e1a5fe66SDerek J. Clark void *data) 200*e1a5fe66SDerek J. Clark { 201*e1a5fe66SDerek J. Clark struct acpi_bus_event *event = data; 202*e1a5fe66SDerek J. Clark struct lwmi_cd01_priv *priv; 203*e1a5fe66SDerek J. Clark int ret; 204*e1a5fe66SDerek J. Clark 205*e1a5fe66SDerek J. Clark if (strcmp(event->device_class, ACPI_AC_CLASS) != 0) 206*e1a5fe66SDerek J. Clark return NOTIFY_DONE; 207*e1a5fe66SDerek J. Clark 208*e1a5fe66SDerek J. Clark priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb); 209*e1a5fe66SDerek J. Clark 210*e1a5fe66SDerek J. Clark switch (event->type) { 211*e1a5fe66SDerek J. Clark case ACPI_AC_NOTIFY_STATUS: 212*e1a5fe66SDerek J. Clark ret = lwmi_cd01_cache(priv); 213*e1a5fe66SDerek J. Clark if (ret) 214*e1a5fe66SDerek J. Clark return NOTIFY_BAD; 215*e1a5fe66SDerek J. Clark 216*e1a5fe66SDerek J. Clark return NOTIFY_OK; 217*e1a5fe66SDerek J. Clark default: 218*e1a5fe66SDerek J. Clark return NOTIFY_DONE; 219*e1a5fe66SDerek J. Clark } 220*e1a5fe66SDerek J. Clark } 221*e1a5fe66SDerek J. Clark 222*e1a5fe66SDerek J. Clark /** 223*e1a5fe66SDerek J. Clark * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block. 224*e1a5fe66SDerek J. Clark * @data: The ACPI event notifier_block to unregister. 225*e1a5fe66SDerek J. Clark */ 226*e1a5fe66SDerek J. Clark static void lwmi_cd01_unregister(void *data) 227*e1a5fe66SDerek J. Clark { 228*e1a5fe66SDerek J. Clark struct notifier_block *acpi_nb = data; 229*e1a5fe66SDerek J. Clark 230*e1a5fe66SDerek J. Clark unregister_acpi_notifier(acpi_nb); 231*e1a5fe66SDerek J. Clark } 232*e1a5fe66SDerek J. Clark 233*e1a5fe66SDerek J. Clark static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) 234*e1a5fe66SDerek J. Clark 235*e1a5fe66SDerek J. Clark { 236*e1a5fe66SDerek J. Clark struct lwmi_cd01_priv *priv; 237*e1a5fe66SDerek J. Clark int ret; 238*e1a5fe66SDerek J. Clark 239*e1a5fe66SDerek J. Clark priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); 240*e1a5fe66SDerek J. Clark if (!priv) 241*e1a5fe66SDerek J. Clark return -ENOMEM; 242*e1a5fe66SDerek J. Clark 243*e1a5fe66SDerek J. Clark priv->wdev = wdev; 244*e1a5fe66SDerek J. Clark dev_set_drvdata(&wdev->dev, priv); 245*e1a5fe66SDerek J. Clark 246*e1a5fe66SDerek J. Clark ret = lwmi_cd01_setup(priv); 247*e1a5fe66SDerek J. Clark if (ret) 248*e1a5fe66SDerek J. Clark return ret; 249*e1a5fe66SDerek J. Clark 250*e1a5fe66SDerek J. Clark priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; 251*e1a5fe66SDerek J. Clark 252*e1a5fe66SDerek J. Clark ret = register_acpi_notifier(&priv->acpi_nb); 253*e1a5fe66SDerek J. Clark if (ret) 254*e1a5fe66SDerek J. Clark return ret; 255*e1a5fe66SDerek J. Clark 256*e1a5fe66SDerek J. Clark ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb); 257*e1a5fe66SDerek J. Clark if (ret) 258*e1a5fe66SDerek J. Clark return ret; 259*e1a5fe66SDerek J. Clark 260*e1a5fe66SDerek J. Clark return component_add(&wdev->dev, &lwmi_cd01_component_ops); 261*e1a5fe66SDerek J. Clark } 262*e1a5fe66SDerek J. Clark 263*e1a5fe66SDerek J. Clark static void lwmi_cd01_remove(struct wmi_device *wdev) 264*e1a5fe66SDerek J. Clark { 265*e1a5fe66SDerek J. Clark component_del(&wdev->dev, &lwmi_cd01_component_ops); 266*e1a5fe66SDerek J. Clark } 267*e1a5fe66SDerek J. Clark 268*e1a5fe66SDerek J. Clark static const struct wmi_device_id lwmi_cd01_id_table[] = { 269*e1a5fe66SDerek J. Clark { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, 270*e1a5fe66SDerek J. Clark {} 271*e1a5fe66SDerek J. Clark }; 272*e1a5fe66SDerek J. Clark 273*e1a5fe66SDerek J. Clark static struct wmi_driver lwmi_cd01_driver = { 274*e1a5fe66SDerek J. Clark .driver = { 275*e1a5fe66SDerek J. Clark .name = "lenovo_wmi_cd01", 276*e1a5fe66SDerek J. Clark .probe_type = PROBE_PREFER_ASYNCHRONOUS, 277*e1a5fe66SDerek J. Clark }, 278*e1a5fe66SDerek J. Clark .id_table = lwmi_cd01_id_table, 279*e1a5fe66SDerek J. Clark .probe = lwmi_cd01_probe, 280*e1a5fe66SDerek J. Clark .remove = lwmi_cd01_remove, 281*e1a5fe66SDerek J. Clark .no_singleton = true, 282*e1a5fe66SDerek J. Clark }; 283*e1a5fe66SDerek J. Clark 284*e1a5fe66SDerek J. Clark /** 285*e1a5fe66SDerek J. Clark * lwmi_cd01_match() - Match rule for the master driver. 286*e1a5fe66SDerek J. Clark * @dev: Pointer to the capability data 01 parent device. 287*e1a5fe66SDerek J. Clark * @data: Unused void pointer for passing match criteria. 288*e1a5fe66SDerek J. Clark * 289*e1a5fe66SDerek J. Clark * Return: int. 290*e1a5fe66SDerek J. Clark */ 291*e1a5fe66SDerek J. Clark int lwmi_cd01_match(struct device *dev, void *data) 292*e1a5fe66SDerek J. Clark { 293*e1a5fe66SDerek J. Clark return dev->driver == &lwmi_cd01_driver.driver; 294*e1a5fe66SDerek J. Clark } 295*e1a5fe66SDerek J. Clark EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01"); 296*e1a5fe66SDerek J. Clark 297*e1a5fe66SDerek J. Clark module_wmi_driver(lwmi_cd01_driver); 298*e1a5fe66SDerek J. Clark 299*e1a5fe66SDerek J. Clark MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table); 300*e1a5fe66SDerek J. Clark MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); 301*e1a5fe66SDerek J. Clark MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); 302*e1a5fe66SDerek J. Clark MODULE_LICENSE("GPL"); 303