dell-pc.c 7.9 KB

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