From 927fc317025a2d0adb7700a6b30b73fd9bffaf58 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 3 Oct 2023 09:35:02 +0200 Subject: [PATCH 1/2] platform/apple: smc: Add apple_smc_write_f32_scaled Signed-off-by: Janne Grunau --- drivers/platform/apple/smc_core.c | 41 +++++++++++++++++++++++++++++++ include/linux/mfd/macsmc.h | 1 + 2 files changed, 42 insertions(+) diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c index adef33e152530f..ae85ef2aad9d33 100644 --- a/drivers/platform/apple/smc_core.c +++ b/drivers/platform/apple/smc_core.c @@ -149,6 +149,47 @@ int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int sc } EXPORT_SYMBOL(apple_smc_read_f32_scaled); +#define FLT_SIGN_MASK BIT(31) +#define FLT_EXP_MASK GENMASK(30, 23) +#define FLT_MANT_MASK GENMASK(22, 0) +#define FLT_EXP_BIAS 127 + +int apple_smc_write_f32_scaled(struct apple_smc *smc, smc_key key, int value, + int scale) +{ + u64 val; + u32 fval = 0; + int exp = 0, neg; + + val = abs(value); + neg = val != value; + + if (scale > 1) { + val <<= 32; + exp = 32; + val /= scale; + } else if (scale < 1) + val *= -scale; + + if (val) { + int msb = __fls(val) - exp; + if (msb > 23) { + val >>= msb - 23; + exp -= msb - 23; + } else if (msb < 23) { + val <<= 23 - msb; + exp += msb; + } + + fval = FIELD_PREP(FLT_SIGN_MASK, neg) | + FIELD_PREP(FLT_EXP_MASK, exp + FLT_EXP_BIAS) | + FIELD_PREP(FLT_MANT_MASK, val); + } + + return apple_smc_write_u32(smc, key, fval); +} +EXPORT_SYMBOL(apple_smc_write_f32_scaled); + /* * ioft is a 48.16 fixed point type */ diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h index a63da99ed5ed2d..b4efba685d8cff 100644 --- a/include/linux/mfd/macsmc.h +++ b/include/linux/mfd/macsmc.h @@ -83,6 +83,7 @@ static inline int apple_smc_read_flag(struct apple_smc *smc, smc_key key) #define apple_smc_write_flag apple_smc_write_u8 int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int scale); +int apple_smc_write_f32_scaled(struct apple_smc *smc, smc_key key, int p, int scale); int apple_smc_read_ioft_scaled(struct apple_smc *smc, smc_key key, u64 *p, int scale); int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n); From 24372f4bc5477c7c6799bf8c33280d9e5db1b4c9 Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 5 Aug 2024 20:58:50 +1000 Subject: [PATCH 2/2] hwmon: macsmc: wire up manual fan control support The SMC provides an interface for manually controlling the speeds of any fans attached to it. Expose this via the standard hwmon interface. Once a fan is in manual control, the SMC makes no attempts to save users from themselves. It is possible to write arbitrary values outside of the SMC's reported safe range. The driver therefore does its own sanity checking. Since we are unsure whether or not leaving the fans in manual mode can cause damage to Apple Silicon devices, this functionality is gated behind a very explicit and scary-sounding unsafe module parameter. Signed-off-by: James Calligeros --- drivers/hwmon/macsmc-hwmon.c | 116 ++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c index f0961e5912eeb2..53f0264d88d079 100644 --- a/drivers/hwmon/macsmc-hwmon.c +++ b/drivers/hwmon/macsmc-hwmon.c @@ -30,6 +30,10 @@ #define MAX_LABEL_LENGTH 32 #define NUM_SENSOR_TYPES 5 /* temp, volt, current, power, fan */ +static bool melt_my_mac; +module_param_unsafe(melt_my_mac, bool, 0644); +MODULE_PARM_DESC(melt_my_mac, "Override the SMC to set your own fan speeds on supported machines"); + struct macsmc_hwmon_sensor { struct apple_smc_key_info info; smc_key macsmc_key; @@ -41,8 +45,10 @@ struct macsmc_hwmon_fan { struct macsmc_hwmon_sensor min; struct macsmc_hwmon_sensor max; struct macsmc_hwmon_sensor set; + struct macsmc_hwmon_sensor mode; char label[MAX_LABEL_LENGTH]; u32 attrs; + bool manual; }; struct macsmc_hwmon_sensors { @@ -152,6 +158,21 @@ static int macsmc_hwmon_read_key(struct apple_smc *smc, return 0; } +static int macsmc_hwmon_write_key(struct apple_smc *smc, + struct macsmc_hwmon_sensor *sensor, long val, + int scale) +{ + switch (sensor->info.type_code) { + /* 32-bit IEEE 754 float */ + case __SMC_KEY('f', 'l', 't', ' '): + return apple_smc_write_f32_scaled(smc, sensor->macsmc_key, val, scale); + case __SMC_KEY('u', 'i', '8', ' '): + return apple_smc_write_u8(smc, sensor->macsmc_key, val); + default: + return -EOPNOTSUPP; + } +} + static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan, long *val) { if (!(hwmon->fan.fans[chan].attrs & BIT(attr))) @@ -175,6 +196,61 @@ static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan, } } +static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, long val) +{ + struct macsmc_hwmon *hwmon = dev_get_drvdata(dev); + int ret = 0; + long min = 0; + long max = 0; + + if (!melt_my_mac || + hwmon->fan.fans[channel].mode.macsmc_key == 0) + return -EOPNOTSUPP; + + if ((channel >= hwmon->fan.n_fans) || + !(hwmon->fan.fans[channel].attrs & BIT(attr)) || + (attr != hwmon_fan_target)) + return -EINVAL; + + /* + * The SMC does no sanity checks on requested fan speeds, so we need to. + */ + ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].min, 1, &min); + if (ret) + return ret; + ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].max, 1, &max); + if (ret) + return ret; + + if (val >= min && val <= max) { + if (!hwmon->fan.fans[channel].manual) { + /* Write 1 to mode key for manual control */ + ret = macsmc_hwmon_write_key(hwmon->smc, &hwmon->fan.fans[channel].mode, 1, 1); + if (ret < 0) + return ret; + + hwmon->fan.fans[channel].manual = true; + dev_info(dev, "Fan %d now under manual control! Set target speed to 0 for automatic control.\n", + channel + 1); + } + return macsmc_hwmon_write_key(hwmon->smc, &hwmon->fan.fans[channel].set, val, 1); + } else if (!val) { + if (hwmon->fan.fans[channel].manual) { + dev_info(dev, "Returning control of fan %d to SMC.\n", channel + 1); + ret = macsmc_hwmon_write_key(hwmon->smc, &hwmon->fan.fans[channel].mode, 0, 1); + if (ret < 0) + return ret; + + hwmon->fan.fans[channel].manual = false; + } + } else { + dev_err(dev, "Requested fan speed %ld out of range [%ld, %ld]", val, min, max); + return -EINVAL; + } + + return 0; +} + static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -212,13 +288,38 @@ static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, static int macsmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { - return -EOPNOTSUPP; + switch (type) { + case hwmon_fan: + return macsmc_hwmon_write_fan(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t macsmc_hwmon_fan_is_visible(const void *data, u32 attr, int channel) +{ + const struct macsmc_hwmon *hwmon = data; + + if (channel >= hwmon->fan.n_fans) + return -EINVAL; + + if (melt_my_mac && attr == hwmon_fan_target && hwmon->fan.fans[channel].mode.macsmc_key != 0) + return 0644; + + return 0444; } static umode_t macsmc_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { + switch (type) { + case hwmon_fan: + return macsmc_hwmon_fan_is_visible(data, attr, channel); + default: + break; + } + return 0444; } @@ -292,6 +393,7 @@ static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc, const char *min; const char *max; const char *set; + const char *mode; int ret = 0; ret = of_property_read_string(fan_node, "apple,key-id", &now); @@ -335,6 +437,18 @@ static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc, fan->attrs |= HWMON_F_TARGET; } + ret = of_property_read_string(fan_node, "apple,fan-mode", &mode); + if (ret) + dev_warn(dev, "No fan mode key for %s", fan->label); + else { + ret = macsmc_hwmon_parse_key(dev, smc, &fan->mode, mode); + if (ret) + return ret; + } + + /* Initialise fan control mode to automatic */ + fan->manual = false; + return 0; }