1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Driver for Dell laptop extras 4 * 5 * Copyright (c) Lyndon Sanche <lsanche@lyndeno.ca> 6 * 7 * Based on documentation in the libsmbios package: 8 * Copyright (C) 2005-2014 Dell Inc. 9 */ 10 11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 12 13 #include <linux/bitfield.h> 14 #include <linux/bitops.h> 15 #include <linux/bits.h> 16 #include <linux/device/faux.h> 17 #include <linux/dmi.h> 18 #include <linux/err.h> 19 #include <linux/init.h> 20 #include <linux/kernel.h> 21 #include <linux/module.h> 22 #include <linux/platform_profile.h> 23 #include <linux/slab.h> 24 25 #include "dell-smbios.h" 26 27 static struct faux_device *dell_pc_fdev; 28 static int supported_modes; 29 30 static const struct dmi_system_id dell_device_table[] __initconst = { 31 { 32 .ident = "Dell Inc.", 33 .matches = { 34 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 35 }, 36 }, 37 { 38 .ident = "Dell Computer Corporation", 39 .matches = { 40 DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), 41 }, 42 }, 43 { } 44 }; 45 MODULE_DEVICE_TABLE(dmi, dell_device_table); 46 47 /* Derived from smbios-thermal-ctl 48 * 49 * cbClass 17 50 * cbSelect 19 51 * User Selectable Thermal Tables(USTT) 52 * cbArg1 determines the function to be performed 53 * cbArg1 0x0 = Get Thermal Information 54 * cbRES1 Standard return codes (0, -1, -2) 55 * cbRES2, byte 0 Bitmap of supported thermal modes. A mode is supported if 56 * its bit is set to 1 57 * Bit 0 Balanced 58 * Bit 1 Cool Bottom 59 * Bit 2 Quiet 60 * Bit 3 Performance 61 * cbRES2, byte 1 Bitmap of supported Active Acoustic Controller (AAC) modes. 62 * Each mode corresponds to the supported thermal modes in 63 * byte 0. A mode is supported if its bit is set to 1. 64 * Bit 0 AAC (Balanced) 65 * Bit 1 AAC (Cool Bottom 66 * Bit 2 AAC (Quiet) 67 * Bit 3 AAC (Performance) 68 * cbRes3, byte 0 Current Thermal Mode 69 * Bit 0 Balanced 70 * Bit 1 Cool Bottom 71 * Bit 2 Quiet 72 * Bit 3 Performanc 73 * cbRes3, byte 1 AAC Configuration type 74 * 0 Global (AAC enable/disable applies to all supported USTT modes) 75 * 1 USTT mode specific 76 * cbRes3, byte 2 Current Active Acoustic Controller (AAC) Mode 77 * If AAC Configuration Type is Global, 78 * 0 AAC mode disabled 79 * 1 AAC mode enabled 80 * If AAC Configuration Type is USTT mode specific (multiple bits may be set), 81 * Bit 0 AAC (Balanced) 82 * Bit 1 AAC (Cool Bottom 83 * Bit 2 AAC (Quiet) 84 * Bit 3 AAC (Performance) 85 * cbRes3, byte 3 Current Fan Failure Mode 86 * Bit 0 Minimal Fan Failure (at least one fan has failed, one fan working) 87 * Bit 1 Catastrophic Fan Failure (all fans have failed) 88 * 89 * cbArg1 0x1 (Set Thermal Information), both desired thermal mode and 90 * desired AAC mode shall be applied 91 * cbArg2, byte 0 Desired Thermal Mode to set 92 * (only one bit may be set for this parameter) 93 * Bit 0 Balanced 94 * Bit 1 Cool Bottom 95 * Bit 2 Quiet 96 * Bit 3 Performance 97 * cbArg2, byte 1 Desired Active Acoustic Controller (AAC) Mode to set 98 * If AAC Configuration Type is Global, 99 * 0 AAC mode disabled 100 * 1 AAC mode enabled 101 * If AAC Configuration Type is USTT mode specific 102 * (multiple bits may be set for this parameter), 103 * Bit 0 AAC (Balanced) 104 * Bit 1 AAC (Cool Bottom 105 * Bit 2 AAC (Quiet) 106 * Bit 3 AAC (Performance) 107 */ 108 109 #define DELL_ACC_GET_FIELD GENMASK(19, 16) 110 #define DELL_ACC_SET_FIELD GENMASK(11, 8) 111 #define DELL_THERMAL_SUPPORTED GENMASK(3, 0) 112 113 enum thermal_mode_bits { 114 DELL_BALANCED = BIT(0), 115 DELL_COOL_BOTTOM = BIT(1), 116 DELL_QUIET = BIT(2), 117 DELL_PERFORMANCE = BIT(3), 118 }; 119 120 static int thermal_get_mode(void) 121 { 122 struct calling_interface_buffer buffer; 123 int state; 124 int ret; 125 126 dell_fill_request(&buffer, 0x0, 0, 0, 0); 127 ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); 128 if (ret) 129 return ret; 130 state = buffer.output[2]; 131 if (state & DELL_BALANCED) 132 return DELL_BALANCED; 133 else if (state & DELL_COOL_BOTTOM) 134 return DELL_COOL_BOTTOM; 135 else if (state & DELL_QUIET) 136 return DELL_QUIET; 137 else if (state & DELL_PERFORMANCE) 138 return DELL_PERFORMANCE; 139 else 140 return -ENXIO; 141 } 142 143 static int thermal_get_supported_modes(int *supported_bits) 144 { 145 struct calling_interface_buffer buffer; 146 int ret; 147 148 dell_fill_request(&buffer, 0x0, 0, 0, 0); 149 ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); 150 if (ret) 151 return ret; 152 *supported_bits = FIELD_GET(DELL_THERMAL_SUPPORTED, buffer.output[1]); 153 return 0; 154 } 155 156 static int thermal_get_acc_mode(int *acc_mode) 157 { 158 struct calling_interface_buffer buffer; 159 int ret; 160 161 dell_fill_request(&buffer, 0x0, 0, 0, 0); 162 ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); 163 if (ret) 164 return ret; 165 *acc_mode = FIELD_GET(DELL_ACC_GET_FIELD, buffer.output[3]); 166 return 0; 167 } 168 169 static int thermal_set_mode(enum thermal_mode_bits state) 170 { 171 struct calling_interface_buffer buffer; 172 int ret; 173 int acc_mode; 174 175 ret = thermal_get_acc_mode(&acc_mode); 176 if (ret) 177 return ret; 178 179 dell_fill_request(&buffer, 0x1, FIELD_PREP(DELL_ACC_SET_FIELD, acc_mode) | state, 0, 0); 180 return dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); 181 } 182 183 static int thermal_platform_profile_set(struct device *dev, 184 enum platform_profile_option profile) 185 { 186 switch (profile) { 187 case PLATFORM_PROFILE_BALANCED: 188 return thermal_set_mode(DELL_BALANCED); 189 case PLATFORM_PROFILE_PERFORMANCE: 190 return thermal_set_mode(DELL_PERFORMANCE); 191 case PLATFORM_PROFILE_QUIET: 192 return thermal_set_mode(DELL_QUIET); 193 case PLATFORM_PROFILE_COOL: 194 return thermal_set_mode(DELL_COOL_BOTTOM); 195 default: 196 return -EOPNOTSUPP; 197 } 198 } 199 200 static int thermal_platform_profile_get(struct device *dev, 201 enum platform_profile_option *profile) 202 { 203 int ret; 204 205 ret = thermal_get_mode(); 206 if (ret < 0) 207 return ret; 208 209 switch (ret) { 210 case DELL_BALANCED: 211 *profile = PLATFORM_PROFILE_BALANCED; 212 break; 213 case DELL_PERFORMANCE: 214 *profile = PLATFORM_PROFILE_PERFORMANCE; 215 break; 216 case DELL_COOL_BOTTOM: 217 *profile = PLATFORM_PROFILE_COOL; 218 break; 219 case DELL_QUIET: 220 *profile = PLATFORM_PROFILE_QUIET; 221 break; 222 default: 223 return -EINVAL; 224 } 225 226 return 0; 227 } 228 229 static int thermal_platform_profile_probe(void *drvdata, unsigned long *choices) 230 { 231 if (supported_modes & DELL_QUIET) 232 __set_bit(PLATFORM_PROFILE_QUIET, choices); 233 if (supported_modes & DELL_COOL_BOTTOM) 234 __set_bit(PLATFORM_PROFILE_COOL, choices); 235 if (supported_modes & DELL_BALANCED) 236 __set_bit(PLATFORM_PROFILE_BALANCED, choices); 237 if (supported_modes & DELL_PERFORMANCE) 238 __set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); 239 240 return 0; 241 } 242 243 static const struct platform_profile_ops dell_pc_platform_profile_ops = { 244 .probe = thermal_platform_profile_probe, 245 .profile_get = thermal_platform_profile_get, 246 .profile_set = thermal_platform_profile_set, 247 }; 248 249 static int dell_pc_faux_probe(struct faux_device *fdev) 250 { 251 struct device *ppdev; 252 int ret; 253 254 if (!dell_smbios_class_is_supported(CLASS_INFO)) 255 return -ENODEV; 256 257 ret = thermal_get_supported_modes(&supported_modes); 258 if (ret < 0) 259 return ret; 260 261 ppdev = devm_platform_profile_register(&fdev->dev, "dell-pc", NULL, 262 &dell_pc_platform_profile_ops); 263 264 return PTR_ERR_OR_ZERO(ppdev); 265 } 266 267 static const struct faux_device_ops dell_pc_faux_ops = { 268 .probe = dell_pc_faux_probe, 269 }; 270 271 static int __init dell_init(void) 272 { 273 if (!dmi_check_system(dell_device_table)) 274 return -ENODEV; 275 276 dell_pc_fdev = faux_device_create("dell-pc", NULL, &dell_pc_faux_ops); 277 if (!dell_pc_fdev) 278 return -ENODEV; 279 280 return 0; 281 } 282 283 static void __exit dell_exit(void) 284 { 285 faux_device_destroy(dell_pc_fdev); 286 } 287 288 module_init(dell_init); 289 module_exit(dell_exit); 290 291 MODULE_AUTHOR("Lyndon Sanche <lsanche@lyndeno.ca>"); 292 MODULE_DESCRIPTION("Dell PC driver"); 293 MODULE_LICENSE("GPL"); 294