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