| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * acerhdf - A driver which monitors the temperature |
|---|
| 3 | 4 | * of the aspire one netbook, turns on/off the fan |
|---|
| 4 | 5 | * as soon as the upper/lower threshold is reached. |
|---|
| 5 | 6 | * |
|---|
| 6 | | - * (C) 2009 - Peter Feuerer peter (a) piie.net |
|---|
| 7 | | - * http://piie.net |
|---|
| 7 | + * (C) 2009 - Peter Kaestle peter (a) piie.net |
|---|
| 8 | + * https://piie.net |
|---|
| 8 | 9 | * 2009 Borislav Petkov bp (a) alien8.de |
|---|
| 9 | 10 | * |
|---|
| 10 | 11 | * Inspired by and many thanks to: |
|---|
| .. | .. |
|---|
| 15 | 16 | * o lkml - Matthew Garrett |
|---|
| 16 | 17 | * - Borislav Petkov |
|---|
| 17 | 18 | * - Andreas Mohr |
|---|
| 18 | | - * |
|---|
| 19 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 20 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 21 | | - * the Free Software Foundation; either version 2 of the License, or |
|---|
| 22 | | - * (at your option) any later version. |
|---|
| 23 | | - * |
|---|
| 24 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 25 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 26 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 27 | | - * GNU General Public License for more details. |
|---|
| 28 | | - * |
|---|
| 29 | | - * You should have received a copy of the GNU General Public License |
|---|
| 30 | | - * along with this program; if not, write to the Free Software |
|---|
| 31 | | - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 32 | 19 | */ |
|---|
| 33 | 20 | |
|---|
| 34 | 21 | #define pr_fmt(fmt) "acerhdf: " fmt |
|---|
| .. | .. |
|---|
| 86 | 73 | static unsigned int fanon = 60000; |
|---|
| 87 | 74 | static unsigned int fanoff = 53000; |
|---|
| 88 | 75 | static unsigned int verbose; |
|---|
| 76 | +static unsigned int list_supported; |
|---|
| 89 | 77 | static unsigned int fanstate = ACERHDF_FAN_AUTO; |
|---|
| 90 | 78 | static char force_bios[16]; |
|---|
| 91 | 79 | static char force_product[16]; |
|---|
| .. | .. |
|---|
| 104 | 92 | MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature"); |
|---|
| 105 | 93 | module_param(verbose, uint, 0600); |
|---|
| 106 | 94 | MODULE_PARM_DESC(verbose, "Enable verbose dmesg output"); |
|---|
| 95 | +module_param(list_supported, uint, 0600); |
|---|
| 96 | +MODULE_PARM_DESC(list_supported, "List supported models and BIOS versions"); |
|---|
| 107 | 97 | module_param_string(force_bios, force_bios, 16, 0); |
|---|
| 108 | | -MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check"); |
|---|
| 98 | +MODULE_PARM_DESC(force_bios, "Pretend system has this known supported BIOS version"); |
|---|
| 109 | 99 | module_param_string(force_product, force_product, 16, 0); |
|---|
| 110 | | -MODULE_PARM_DESC(force_product, "Force BIOS product and omit BIOS check"); |
|---|
| 100 | +MODULE_PARM_DESC(force_product, "Pretend system is this known supported model"); |
|---|
| 111 | 101 | |
|---|
| 112 | 102 | /* |
|---|
| 113 | 103 | * cmd_off: to switch the fan completely off and check if the fan is off |
|---|
| .. | .. |
|---|
| 130 | 120 | .moff = 0xff, |
|---|
| 131 | 121 | }; |
|---|
| 132 | 122 | |
|---|
| 133 | | -/* BIOS settings */ |
|---|
| 123 | +/* BIOS settings - only used during probe */ |
|---|
| 134 | 124 | struct bios_settings { |
|---|
| 135 | 125 | const char *vendor; |
|---|
| 136 | 126 | const char *product; |
|---|
| .. | .. |
|---|
| 141 | 131 | int mcmd_enable; |
|---|
| 142 | 132 | }; |
|---|
| 143 | 133 | |
|---|
| 134 | +/* This could be a daughter struct in the above, but not worth the redirect */ |
|---|
| 135 | +struct ctrl_settings { |
|---|
| 136 | + u8 fanreg; |
|---|
| 137 | + u8 tempreg; |
|---|
| 138 | + struct fancmd cmd; |
|---|
| 139 | + int mcmd_enable; |
|---|
| 140 | +}; |
|---|
| 141 | + |
|---|
| 142 | +static struct ctrl_settings ctrl_cfg __read_mostly; |
|---|
| 143 | + |
|---|
| 144 | 144 | /* Register addresses and values for different BIOS versions */ |
|---|
| 145 | | -static const struct bios_settings bios_tbl[] = { |
|---|
| 145 | +static const struct bios_settings bios_tbl[] __initconst = { |
|---|
| 146 | 146 | /* AOA110 */ |
|---|
| 147 | 147 | {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00}, 0}, |
|---|
| 148 | 148 | {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00}, 0}, |
|---|
| .. | .. |
|---|
| 224 | 224 | {"Acer", "Aspire 5739G", "V1.3311", 0x55, 0x58, {0x20, 0x00}, 0}, |
|---|
| 225 | 225 | /* Acer TravelMate 7730 */ |
|---|
| 226 | 226 | {"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00}, 0}, |
|---|
| 227 | + /* Acer Aspire 7551 */ |
|---|
| 228 | + {"Acer", "Aspire 7551", "V1.18", 0x93, 0xa8, {0x14, 0x04}, 1}, |
|---|
| 227 | 229 | /* Acer TravelMate TM8573T */ |
|---|
| 228 | 230 | {"Acer", "TM8573T", "V1.13", 0x93, 0xa8, {0x14, 0x04}, 1}, |
|---|
| 229 | 231 | /* Gateway */ |
|---|
| .. | .. |
|---|
| 257 | 259 | {"", "", "", 0, 0, {0, 0}, 0} |
|---|
| 258 | 260 | }; |
|---|
| 259 | 261 | |
|---|
| 260 | | -static const struct bios_settings *bios_cfg __read_mostly; |
|---|
| 261 | | - |
|---|
| 262 | 262 | /* |
|---|
| 263 | 263 | * this struct is used to instruct thermal layer to use bang_bang instead of |
|---|
| 264 | 264 | * default governor for acerhdf |
|---|
| .. | .. |
|---|
| 271 | 271 | { |
|---|
| 272 | 272 | u8 read_temp; |
|---|
| 273 | 273 | |
|---|
| 274 | | - if (ec_read(bios_cfg->tempreg, &read_temp)) |
|---|
| 274 | + if (ec_read(ctrl_cfg.tempreg, &read_temp)) |
|---|
| 275 | 275 | return -EINVAL; |
|---|
| 276 | 276 | |
|---|
| 277 | 277 | *temp = read_temp * 1000; |
|---|
| .. | .. |
|---|
| 283 | 283 | { |
|---|
| 284 | 284 | u8 fan; |
|---|
| 285 | 285 | |
|---|
| 286 | | - if (ec_read(bios_cfg->fanreg, &fan)) |
|---|
| 286 | + if (ec_read(ctrl_cfg.fanreg, &fan)) |
|---|
| 287 | 287 | return -EINVAL; |
|---|
| 288 | 288 | |
|---|
| 289 | | - if (fan != bios_cfg->cmd.cmd_off) |
|---|
| 289 | + if (fan != ctrl_cfg.cmd.cmd_off) |
|---|
| 290 | 290 | *state = ACERHDF_FAN_AUTO; |
|---|
| 291 | 291 | else |
|---|
| 292 | 292 | *state = ACERHDF_FAN_OFF; |
|---|
| .. | .. |
|---|
| 307 | 307 | state = ACERHDF_FAN_AUTO; |
|---|
| 308 | 308 | } |
|---|
| 309 | 309 | |
|---|
| 310 | | - cmd = (state == ACERHDF_FAN_OFF) ? bios_cfg->cmd.cmd_off |
|---|
| 311 | | - : bios_cfg->cmd.cmd_auto; |
|---|
| 310 | + cmd = (state == ACERHDF_FAN_OFF) ? ctrl_cfg.cmd.cmd_off |
|---|
| 311 | + : ctrl_cfg.cmd.cmd_auto; |
|---|
| 312 | 312 | fanstate = state; |
|---|
| 313 | 313 | |
|---|
| 314 | | - ec_write(bios_cfg->fanreg, cmd); |
|---|
| 314 | + ec_write(ctrl_cfg.fanreg, cmd); |
|---|
| 315 | 315 | |
|---|
| 316 | | - if (bios_cfg->mcmd_enable && state == ACERHDF_FAN_OFF) { |
|---|
| 316 | + if (ctrl_cfg.mcmd_enable && state == ACERHDF_FAN_OFF) { |
|---|
| 317 | 317 | if (verbose) |
|---|
| 318 | 318 | pr_notice("turning off fan manually\n"); |
|---|
| 319 | 319 | ec_write(mcmd.mreg, mcmd.moff); |
|---|
| .. | .. |
|---|
| 397 | 397 | { |
|---|
| 398 | 398 | acerhdf_change_fanstate(ACERHDF_FAN_AUTO); |
|---|
| 399 | 399 | kernelmode = 0; |
|---|
| 400 | | - if (thz_dev) |
|---|
| 401 | | - thz_dev->polling_delay = 0; |
|---|
| 400 | + |
|---|
| 402 | 401 | pr_notice("kernel mode fan control OFF\n"); |
|---|
| 403 | 402 | } |
|---|
| 404 | 403 | static inline void acerhdf_enable_kernelmode(void) |
|---|
| 405 | 404 | { |
|---|
| 406 | 405 | kernelmode = 1; |
|---|
| 407 | 406 | |
|---|
| 408 | | - thz_dev->polling_delay = interval*1000; |
|---|
| 409 | | - thermal_zone_device_update(thz_dev, THERMAL_EVENT_UNSPECIFIED); |
|---|
| 410 | 407 | pr_notice("kernel mode fan control ON\n"); |
|---|
| 411 | | -} |
|---|
| 412 | | - |
|---|
| 413 | | -static int acerhdf_get_mode(struct thermal_zone_device *thermal, |
|---|
| 414 | | - enum thermal_device_mode *mode) |
|---|
| 415 | | -{ |
|---|
| 416 | | - if (verbose) |
|---|
| 417 | | - pr_notice("kernel mode fan control %d\n", kernelmode); |
|---|
| 418 | | - |
|---|
| 419 | | - *mode = (kernelmode) ? THERMAL_DEVICE_ENABLED |
|---|
| 420 | | - : THERMAL_DEVICE_DISABLED; |
|---|
| 421 | | - |
|---|
| 422 | | - return 0; |
|---|
| 423 | 408 | } |
|---|
| 424 | 409 | |
|---|
| 425 | 410 | /* |
|---|
| .. | .. |
|---|
| 428 | 413 | * the temperature and the fan. |
|---|
| 429 | 414 | * disabled: the BIOS takes control of the fan. |
|---|
| 430 | 415 | */ |
|---|
| 431 | | -static int acerhdf_set_mode(struct thermal_zone_device *thermal, |
|---|
| 432 | | - enum thermal_device_mode mode) |
|---|
| 416 | +static int acerhdf_change_mode(struct thermal_zone_device *thermal, |
|---|
| 417 | + enum thermal_device_mode mode) |
|---|
| 433 | 418 | { |
|---|
| 434 | 419 | if (mode == THERMAL_DEVICE_DISABLED && kernelmode) |
|---|
| 435 | 420 | acerhdf_revert_to_bios_mode(); |
|---|
| .. | .. |
|---|
| 488 | 473 | .bind = acerhdf_bind, |
|---|
| 489 | 474 | .unbind = acerhdf_unbind, |
|---|
| 490 | 475 | .get_temp = acerhdf_get_ec_temp, |
|---|
| 491 | | - .get_mode = acerhdf_get_mode, |
|---|
| 492 | | - .set_mode = acerhdf_set_mode, |
|---|
| 476 | + .change_mode = acerhdf_change_mode, |
|---|
| 493 | 477 | .get_trip_type = acerhdf_get_trip_type, |
|---|
| 494 | 478 | .get_trip_hyst = acerhdf_get_trip_hyst, |
|---|
| 495 | 479 | .get_trip_temp = acerhdf_get_trip_temp, |
|---|
| .. | .. |
|---|
| 616 | 600 | } |
|---|
| 617 | 601 | |
|---|
| 618 | 602 | /* check hardware */ |
|---|
| 619 | | -static int acerhdf_check_hardware(void) |
|---|
| 603 | +static int __init acerhdf_check_hardware(void) |
|---|
| 620 | 604 | { |
|---|
| 621 | 605 | char const *vendor, *version, *product; |
|---|
| 622 | 606 | const struct bios_settings *bt = NULL; |
|---|
| 607 | + int found = 0; |
|---|
| 623 | 608 | |
|---|
| 624 | 609 | /* get BIOS data */ |
|---|
| 625 | 610 | vendor = dmi_get_system_info(DMI_SYS_VENDOR); |
|---|
| .. | .. |
|---|
| 632 | 617 | } |
|---|
| 633 | 618 | |
|---|
| 634 | 619 | pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER); |
|---|
| 620 | + |
|---|
| 621 | + if (list_supported) { |
|---|
| 622 | + pr_info("List of supported Manufacturer/Model/BIOS:\n"); |
|---|
| 623 | + pr_info("---------------------------------------------------\n"); |
|---|
| 624 | + for (bt = bios_tbl; bt->vendor[0]; bt++) { |
|---|
| 625 | + pr_info("%-13s | %-17s | %-10s\n", bt->vendor, |
|---|
| 626 | + bt->product, bt->version); |
|---|
| 627 | + } |
|---|
| 628 | + pr_info("---------------------------------------------------\n"); |
|---|
| 629 | + return -ECANCELED; |
|---|
| 630 | + } |
|---|
| 635 | 631 | |
|---|
| 636 | 632 | if (force_bios[0]) { |
|---|
| 637 | 633 | version = force_bios; |
|---|
| .. | .. |
|---|
| 658 | 654 | if (str_starts_with(vendor, bt->vendor) && |
|---|
| 659 | 655 | str_starts_with(product, bt->product) && |
|---|
| 660 | 656 | str_starts_with(version, bt->version)) { |
|---|
| 661 | | - bios_cfg = bt; |
|---|
| 657 | + found = 1; |
|---|
| 662 | 658 | break; |
|---|
| 663 | 659 | } |
|---|
| 664 | 660 | } |
|---|
| 665 | 661 | |
|---|
| 666 | | - if (!bios_cfg) { |
|---|
| 662 | + if (!found) { |
|---|
| 667 | 663 | pr_err("unknown (unsupported) BIOS version %s/%s/%s, please report, aborting!\n", |
|---|
| 668 | 664 | vendor, product, version); |
|---|
| 669 | 665 | return -EINVAL; |
|---|
| 670 | 666 | } |
|---|
| 667 | + |
|---|
| 668 | + /* Copy control settings from BIOS table before we free it. */ |
|---|
| 669 | + ctrl_cfg.fanreg = bt->fanreg; |
|---|
| 670 | + ctrl_cfg.tempreg = bt->tempreg; |
|---|
| 671 | + memcpy(&ctrl_cfg.cmd, &bt->cmd, sizeof(struct fancmd)); |
|---|
| 672 | + ctrl_cfg.mcmd_enable = bt->mcmd_enable; |
|---|
| 671 | 673 | |
|---|
| 672 | 674 | /* |
|---|
| 673 | 675 | * if started with kernel mode off, prevent the kernel from switching |
|---|
| .. | .. |
|---|
| 675 | 677 | */ |
|---|
| 676 | 678 | if (!kernelmode) { |
|---|
| 677 | 679 | pr_notice("Fan control off, to enable do:\n"); |
|---|
| 678 | | - pr_notice("echo -n \"enabled\" > /sys/class/thermal/thermal_zone0/mode\n"); |
|---|
| 680 | + pr_notice("echo -n \"enabled\" > /sys/class/thermal/thermal_zoneN/mode # N=0,1,2...\n"); |
|---|
| 679 | 681 | } |
|---|
| 680 | 682 | |
|---|
| 681 | 683 | return 0; |
|---|
| 682 | 684 | } |
|---|
| 683 | 685 | |
|---|
| 684 | | -static int acerhdf_register_platform(void) |
|---|
| 686 | +static int __init acerhdf_register_platform(void) |
|---|
| 685 | 687 | { |
|---|
| 686 | 688 | int err = 0; |
|---|
| 687 | 689 | |
|---|
| .. | .. |
|---|
| 713 | 715 | platform_driver_unregister(&acerhdf_driver); |
|---|
| 714 | 716 | } |
|---|
| 715 | 717 | |
|---|
| 716 | | -static int acerhdf_register_thermal(void) |
|---|
| 718 | +static int __init acerhdf_register_thermal(void) |
|---|
| 717 | 719 | { |
|---|
| 720 | + int ret; |
|---|
| 721 | + |
|---|
| 718 | 722 | cl_dev = thermal_cooling_device_register("acerhdf-fan", NULL, |
|---|
| 719 | 723 | &acerhdf_cooling_ops); |
|---|
| 720 | 724 | |
|---|
| .. | .. |
|---|
| 727 | 731 | (kernelmode) ? interval*1000 : 0); |
|---|
| 728 | 732 | if (IS_ERR(thz_dev)) |
|---|
| 729 | 733 | return -EINVAL; |
|---|
| 734 | + |
|---|
| 735 | + if (kernelmode) |
|---|
| 736 | + ret = thermal_zone_device_enable(thz_dev); |
|---|
| 737 | + else |
|---|
| 738 | + ret = thermal_zone_device_disable(thz_dev); |
|---|
| 739 | + if (ret) |
|---|
| 740 | + return ret; |
|---|
| 730 | 741 | |
|---|
| 731 | 742 | if (strcmp(thz_dev->governor->name, |
|---|
| 732 | 743 | acerhdf_zone_params.governor_name)) { |
|---|
| .. | .. |
|---|
| 785 | 796 | } |
|---|
| 786 | 797 | |
|---|
| 787 | 798 | MODULE_LICENSE("GPL"); |
|---|
| 788 | | -MODULE_AUTHOR("Peter Feuerer"); |
|---|
| 799 | +MODULE_AUTHOR("Peter Kaestle"); |
|---|
| 789 | 800 | MODULE_DESCRIPTION("Aspire One temperature and fan driver"); |
|---|
| 790 | 801 | MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:"); |
|---|
| 791 | 802 | MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:"); |
|---|
| .. | .. |
|---|
| 799 | 810 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*One*753:"); |
|---|
| 800 | 811 | MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5315:"); |
|---|
| 801 | 812 | MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:"); |
|---|
| 813 | +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*7551:"); |
|---|
| 802 | 814 | MODULE_ALIAS("dmi:*:*Acer*:TM8573T:"); |
|---|
| 803 | 815 | MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:"); |
|---|
| 804 | 816 | MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:"); |
|---|
| .. | .. |
|---|
| 808 | 820 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnENBFT*:"); |
|---|
| 809 | 821 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMA*:"); |
|---|
| 810 | 822 | MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTVR46*:"); |
|---|
| 811 | | -MODULE_ALIAS("dmi:*:*Acer*:pnExtensa 5420*:"); |
|---|
| 823 | +MODULE_ALIAS("dmi:*:*Acer*:pnExtensa*5420*:"); |
|---|
| 812 | 824 | |
|---|
| 813 | 825 | module_init(acerhdf_init); |
|---|
| 814 | 826 | module_exit(acerhdf_exit); |
|---|