mirror of https://github.com/torvalds/linux.git
852 lines
21 KiB
C
852 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0-only OR MIT
|
|
/*
|
|
* Apple SMC hwmon driver for Apple Silicon platforms
|
|
*
|
|
* The System Management Controller on Apple Silicon devices is responsible for
|
|
* measuring data from sensors across the SoC and machine. These include power,
|
|
* temperature, voltage and current sensors. Some "sensors" actually expose
|
|
* derived values. An example of this is the key PHPC, which is an estimate
|
|
* of the heat energy being dissipated by the SoC.
|
|
*
|
|
* While each SoC only has one SMC variant, each platform exposes a different
|
|
* set of sensors. For example, M1 MacBooks expose battery telemetry sensors
|
|
* which are not present on the M1 Mac mini. For this reason, the available
|
|
* sensors for a given platform are described in the device tree in a child
|
|
* node of the SMC device. We must walk this list of available sensors and
|
|
* populate the required hwmon data structures at runtime.
|
|
*
|
|
* Originally based on a concept by Jean-Francois Bortolotti <jeff@borto.fr>
|
|
*
|
|
* Copyright The Asahi Linux Contributors
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/mfd/macsmc.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#define MAX_LABEL_LENGTH 32
|
|
|
|
/* Temperature, voltage, current, power, fan(s) */
|
|
#define NUM_SENSOR_TYPES 5
|
|
|
|
#define FLT_EXP_BIAS 127
|
|
#define FLT_EXP_MASK GENMASK(30, 23)
|
|
#define FLT_MANT_BIAS 23
|
|
#define FLT_MANT_MASK GENMASK(22, 0)
|
|
#define FLT_SIGN_MASK BIT(31)
|
|
|
|
static bool fan_control;
|
|
module_param_unsafe(fan_control, bool, 0644);
|
|
MODULE_PARM_DESC(fan_control,
|
|
"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;
|
|
char label[MAX_LABEL_LENGTH];
|
|
u32 attrs;
|
|
};
|
|
|
|
struct macsmc_hwmon_fan {
|
|
struct macsmc_hwmon_sensor now;
|
|
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 {
|
|
struct hwmon_channel_info channel_info;
|
|
struct macsmc_hwmon_sensor *sensors;
|
|
u32 count;
|
|
};
|
|
|
|
struct macsmc_hwmon_fans {
|
|
struct hwmon_channel_info channel_info;
|
|
struct macsmc_hwmon_fan *fans;
|
|
u32 count;
|
|
};
|
|
|
|
struct macsmc_hwmon {
|
|
struct device *dev;
|
|
struct apple_smc *smc;
|
|
struct device *hwmon_dev;
|
|
struct hwmon_chip_info chip_info;
|
|
/* Chip + sensor types + NULL */
|
|
const struct hwmon_channel_info *channel_infos[1 + NUM_SENSOR_TYPES + 1];
|
|
struct macsmc_hwmon_sensors temp;
|
|
struct macsmc_hwmon_sensors volt;
|
|
struct macsmc_hwmon_sensors curr;
|
|
struct macsmc_hwmon_sensors power;
|
|
struct macsmc_hwmon_fans fan;
|
|
};
|
|
|
|
static int macsmc_hwmon_read_label(struct device *dev,
|
|
enum hwmon_sensor_types type, u32 attr,
|
|
int channel, const char **str)
|
|
{
|
|
struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
|
|
|
|
switch (type) {
|
|
case hwmon_temp:
|
|
*str = hwmon->temp.sensors[channel].label;
|
|
break;
|
|
case hwmon_in:
|
|
*str = hwmon->volt.sensors[channel].label;
|
|
break;
|
|
case hwmon_curr:
|
|
*str = hwmon->curr.sensors[channel].label;
|
|
break;
|
|
case hwmon_power:
|
|
*str = hwmon->power.sensors[channel].label;
|
|
break;
|
|
case hwmon_fan:
|
|
*str = hwmon->fan.fans[channel].label;
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* A number of sensors report data in a 48.16 fixed-point decimal format that is
|
|
* not used by any other function of the SMC.
|
|
*/
|
|
static int macsmc_hwmon_read_ioft_scaled(struct apple_smc *smc, smc_key key,
|
|
u64 *p, int scale)
|
|
{
|
|
u64 val;
|
|
int ret;
|
|
|
|
ret = apple_smc_read_u64(smc, key, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*p = mult_frac(val, scale, 65536);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Many sensors report their data as IEEE-754 floats. No other SMC function uses
|
|
* them.
|
|
*/
|
|
static int macsmc_hwmon_read_f32_scaled(struct apple_smc *smc, smc_key key,
|
|
int *p, int scale)
|
|
{
|
|
u32 fval;
|
|
u64 val;
|
|
int ret, exp;
|
|
|
|
ret = apple_smc_read_u32(smc, key, &fval);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = ((u64)((fval & FLT_MANT_MASK) | BIT(23)));
|
|
exp = ((fval >> 23) & 0xff) - FLT_EXP_BIAS - FLT_MANT_BIAS;
|
|
|
|
/* We never have negatively scaled SMC floats */
|
|
val *= scale;
|
|
|
|
if (exp > 63)
|
|
val = U64_MAX;
|
|
else if (exp < -63)
|
|
val = 0;
|
|
else if (exp < 0)
|
|
val >>= -exp;
|
|
else if (exp != 0 && (val & ~((1UL << (64 - exp)) - 1))) /* overflow */
|
|
val = U64_MAX;
|
|
else
|
|
val <<= exp;
|
|
|
|
if (fval & FLT_SIGN_MASK) {
|
|
if (val > (-(s64)INT_MIN))
|
|
*p = INT_MIN;
|
|
else
|
|
*p = -val;
|
|
} else {
|
|
if (val > INT_MAX)
|
|
*p = INT_MAX;
|
|
else
|
|
*p = val;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The SMC has keys of multiple types, denoted by a FourCC of the same format
|
|
* as the key ID. We don't know what data type a key encodes until we poke at it.
|
|
*/
|
|
static int macsmc_hwmon_read_key(struct apple_smc *smc,
|
|
struct macsmc_hwmon_sensor *sensor, int scale,
|
|
long *val)
|
|
{
|
|
int ret;
|
|
|
|
switch (sensor->info.type_code) {
|
|
/* 32-bit IEEE 754 float */
|
|
case __SMC_KEY('f', 'l', 't', ' '): {
|
|
u32 flt_ = 0;
|
|
|
|
ret = macsmc_hwmon_read_f32_scaled(smc, sensor->macsmc_key,
|
|
&flt_, scale);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*val = flt_;
|
|
break;
|
|
}
|
|
/* 48.16 fixed point decimal */
|
|
case __SMC_KEY('i', 'o', 'f', 't'): {
|
|
u64 ioft = 0;
|
|
|
|
ret = macsmc_hwmon_read_ioft_scaled(smc, sensor->macsmc_key,
|
|
&ioft, scale);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*val = (long)ioft;
|
|
break;
|
|
}
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int macsmc_hwmon_write_f32(struct apple_smc *smc, smc_key key, int value)
|
|
{
|
|
u64 val;
|
|
u32 fval = 0;
|
|
int exp = 0, neg;
|
|
|
|
val = abs(value);
|
|
neg = val != value;
|
|
|
|
if (val) {
|
|
int msb = __fls(val) - exp;
|
|
|
|
if (msb > 23) {
|
|
val >>= msb - FLT_MANT_BIAS;
|
|
exp -= msb - FLT_MANT_BIAS;
|
|
} else if (msb < 23) {
|
|
val <<= FLT_MANT_BIAS - 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);
|
|
}
|
|
|
|
static int macsmc_hwmon_write_key(struct apple_smc *smc,
|
|
struct macsmc_hwmon_sensor *sensor, long val)
|
|
{
|
|
switch (sensor->info.type_code) {
|
|
/* 32-bit IEEE 754 float */
|
|
case __SMC_KEY('f', 'l', 't', ' '):
|
|
return macsmc_hwmon_write_f32(smc, sensor->macsmc_key, val);
|
|
/* unsigned 8-bit integer */
|
|
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)
|
|
{
|
|
switch (attr) {
|
|
case hwmon_fan_input:
|
|
return macsmc_hwmon_read_key(hwmon->smc,
|
|
&hwmon->fan.fans[chan].now, 1, val);
|
|
case hwmon_fan_min:
|
|
return macsmc_hwmon_read_key(hwmon->smc,
|
|
&hwmon->fan.fans[chan].min, 1, val);
|
|
case hwmon_fan_max:
|
|
return macsmc_hwmon_read_key(hwmon->smc,
|
|
&hwmon->fan.fans[chan].max, 1, val);
|
|
case hwmon_fan_target:
|
|
return macsmc_hwmon_read_key(hwmon->smc,
|
|
&hwmon->fan.fans[chan].set, 1, val);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel,
|
|
long val)
|
|
{
|
|
struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
|
|
long min, max;
|
|
int ret;
|
|
|
|
if (!fan_control || hwmon->fan.fans[channel].mode.macsmc_key == 0)
|
|
return -EOPNOTSUPP;
|
|
|
|
/*
|
|
* 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);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
hwmon->fan.fans[channel].manual = true;
|
|
}
|
|
return macsmc_hwmon_write_key(hwmon->smc,
|
|
&hwmon->fan.fans[channel].set, val);
|
|
} else if (!val) {
|
|
if (hwmon->fan.fans[channel].manual) {
|
|
ret = macsmc_hwmon_write_key(hwmon->smc,
|
|
&hwmon->fan.fans[channel].mode, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
hwmon->fan.fans[channel].manual = false;
|
|
}
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|
u32 attr, int channel, long *val)
|
|
{
|
|
struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
switch (type) {
|
|
case hwmon_temp:
|
|
ret = macsmc_hwmon_read_key(hwmon->smc,
|
|
&hwmon->temp.sensors[channel], 1000, val);
|
|
break;
|
|
case hwmon_in:
|
|
ret = macsmc_hwmon_read_key(hwmon->smc,
|
|
&hwmon->volt.sensors[channel], 1000, val);
|
|
break;
|
|
case hwmon_curr:
|
|
ret = macsmc_hwmon_read_key(hwmon->smc,
|
|
&hwmon->curr.sensors[channel], 1000, val);
|
|
break;
|
|
case hwmon_power:
|
|
/* SMC returns power in Watts with acceptable precision to scale to uW */
|
|
ret = macsmc_hwmon_read_key(hwmon->smc,
|
|
&hwmon->power.sensors[channel],
|
|
1000000, val);
|
|
break;
|
|
case hwmon_fan:
|
|
ret = macsmc_hwmon_read_fan(hwmon, attr, channel, val);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int macsmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|
u32 attr, int channel, long val)
|
|
{
|
|
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 struct macsmc_hwmon_fan *fan,
|
|
u32 attr)
|
|
{
|
|
if (fan->attrs & BIT(attr)) {
|
|
if (attr == hwmon_fan_target && fan_control && fan->mode.macsmc_key)
|
|
return 0644;
|
|
|
|
return 0444;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static umode_t macsmc_hwmon_is_visible(const void *data,
|
|
enum hwmon_sensor_types type, u32 attr,
|
|
int channel)
|
|
{
|
|
const struct macsmc_hwmon *hwmon = data;
|
|
struct macsmc_hwmon_sensor *sensor;
|
|
|
|
switch (type) {
|
|
case hwmon_in:
|
|
sensor = &hwmon->volt.sensors[channel];
|
|
break;
|
|
case hwmon_curr:
|
|
sensor = &hwmon->curr.sensors[channel];
|
|
break;
|
|
case hwmon_power:
|
|
sensor = &hwmon->power.sensors[channel];
|
|
break;
|
|
case hwmon_temp:
|
|
sensor = &hwmon->temp.sensors[channel];
|
|
break;
|
|
case hwmon_fan:
|
|
return macsmc_hwmon_fan_is_visible(&hwmon->fan.fans[channel], attr);
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
/* Sensors only register ro attributes */
|
|
if (sensor->attrs & BIT(attr))
|
|
return 0444;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct hwmon_ops macsmc_hwmon_ops = {
|
|
.is_visible = macsmc_hwmon_is_visible,
|
|
.read = macsmc_hwmon_read,
|
|
.read_string = macsmc_hwmon_read_label,
|
|
.write = macsmc_hwmon_write,
|
|
};
|
|
|
|
/*
|
|
* Get the key metadata, including key data type, from the SMC.
|
|
*/
|
|
static int macsmc_hwmon_parse_key(struct device *dev, struct apple_smc *smc,
|
|
struct macsmc_hwmon_sensor *sensor,
|
|
const char *key)
|
|
{
|
|
int ret;
|
|
|
|
ret = apple_smc_get_key_info(smc, _SMC_KEY(key), &sensor->info);
|
|
if (ret) {
|
|
dev_dbg(dev, "Failed to retrieve key info for %s\n", key);
|
|
return ret;
|
|
}
|
|
|
|
sensor->macsmc_key = _SMC_KEY(key);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* A sensor is a single key-value pair as made available by the SMC.
|
|
* The devicetree gives us the SMC key ID and a friendly name where the
|
|
* purpose of the sensor is known.
|
|
*/
|
|
static int macsmc_hwmon_create_sensor(struct device *dev, struct apple_smc *smc,
|
|
struct device_node *sensor_node,
|
|
struct macsmc_hwmon_sensor *sensor)
|
|
{
|
|
const char *key, *label;
|
|
int ret;
|
|
|
|
ret = of_property_read_string(sensor_node, "apple,key-id", &key);
|
|
if (ret) {
|
|
dev_dbg(dev, "Could not find apple,key-id in sensor node\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = macsmc_hwmon_parse_key(dev, smc, sensor, key);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = of_property_read_string(sensor_node, "label", &label);
|
|
if (ret)
|
|
dev_dbg(dev, "No label found for sensor %s\n", key);
|
|
else
|
|
strscpy_pad(sensor->label, label, sizeof(sensor->label));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Fan data is exposed by the SMC as multiple sensors.
|
|
*
|
|
* The devicetree schema reuses apple,key-id for the actual fan speed sensor.
|
|
* Min, max and target keys do not need labels, so we can reuse label
|
|
* for naming the entire fan.
|
|
*/
|
|
static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc,
|
|
struct device_node *fan_node,
|
|
struct macsmc_hwmon_fan *fan)
|
|
{
|
|
const char *label, *now, *min, *max, *set, *mode;
|
|
int ret;
|
|
|
|
ret = of_property_read_string(fan_node, "apple,key-id", &now);
|
|
if (ret) {
|
|
dev_err(dev, "apple,key-id not found in fan node!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = macsmc_hwmon_parse_key(dev, smc, &fan->now, now);
|
|
if (ret)
|
|
return ret;
|
|
|
|
fan->attrs = HWMON_F_INPUT;
|
|
|
|
ret = of_property_read_string(fan_node, "label", &label);
|
|
if (ret) {
|
|
dev_dbg(dev, "No label found for fan %s\n", now);
|
|
} else {
|
|
strscpy_pad(fan->label, label, sizeof(fan->label));
|
|
fan->attrs |= HWMON_F_LABEL;
|
|
}
|
|
|
|
/* The following keys are not required to simply monitor fan speed */
|
|
if (!of_property_read_string(fan_node, "apple,fan-minimum", &min)) {
|
|
ret = macsmc_hwmon_parse_key(dev, smc, &fan->min, min);
|
|
if (ret)
|
|
return ret;
|
|
|
|
fan->attrs |= HWMON_F_MIN;
|
|
}
|
|
|
|
if (!of_property_read_string(fan_node, "apple,fan-maximum", &max)) {
|
|
ret = macsmc_hwmon_parse_key(dev, smc, &fan->max, max);
|
|
if (ret)
|
|
return ret;
|
|
|
|
fan->attrs |= HWMON_F_MAX;
|
|
}
|
|
|
|
if (!of_property_read_string(fan_node, "apple,fan-target", &set)) {
|
|
ret = macsmc_hwmon_parse_key(dev, smc, &fan->set, set);
|
|
if (ret)
|
|
return ret;
|
|
|
|
fan->attrs |= HWMON_F_TARGET;
|
|
}
|
|
|
|
if (!of_property_read_string(fan_node, "apple,fan-mode", &mode)) {
|
|
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;
|
|
}
|
|
|
|
static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon,
|
|
struct device_node *hwmon_node)
|
|
{
|
|
struct device_node *key_node __maybe_unused;
|
|
struct macsmc_hwmon_sensor *sensor;
|
|
u32 n_current = 0, n_fan = 0, n_power = 0, n_temperature = 0, n_voltage = 0;
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "current-") {
|
|
n_current++;
|
|
}
|
|
|
|
if (n_current) {
|
|
hwmon->curr.sensors = devm_kcalloc(hwmon->dev, n_current,
|
|
sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL);
|
|
if (!hwmon->curr.sensors)
|
|
return -ENOMEM;
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "current-") {
|
|
sensor = &hwmon->curr.sensors[hwmon->curr.count];
|
|
if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
|
|
sensor->attrs = HWMON_C_INPUT;
|
|
|
|
if (*sensor->label)
|
|
sensor->attrs |= HWMON_C_LABEL;
|
|
|
|
hwmon->curr.count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "fan-") {
|
|
n_fan++;
|
|
}
|
|
|
|
if (n_fan) {
|
|
hwmon->fan.fans = devm_kcalloc(hwmon->dev, n_fan,
|
|
sizeof(struct macsmc_hwmon_fan), GFP_KERNEL);
|
|
if (!hwmon->fan.fans)
|
|
return -ENOMEM;
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "fan-") {
|
|
if (!macsmc_hwmon_create_fan(hwmon->dev, hwmon->smc, key_node,
|
|
&hwmon->fan.fans[hwmon->fan.count]))
|
|
hwmon->fan.count++;
|
|
}
|
|
}
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "power-") {
|
|
n_power++;
|
|
}
|
|
|
|
if (n_power) {
|
|
hwmon->power.sensors = devm_kcalloc(hwmon->dev, n_power,
|
|
sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL);
|
|
if (!hwmon->power.sensors)
|
|
return -ENOMEM;
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "power-") {
|
|
sensor = &hwmon->power.sensors[hwmon->power.count];
|
|
if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
|
|
sensor->attrs = HWMON_P_INPUT;
|
|
|
|
if (*sensor->label)
|
|
sensor->attrs |= HWMON_P_LABEL;
|
|
|
|
hwmon->power.count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "temperature-") {
|
|
n_temperature++;
|
|
}
|
|
|
|
if (n_temperature) {
|
|
hwmon->temp.sensors = devm_kcalloc(hwmon->dev, n_temperature,
|
|
sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL);
|
|
if (!hwmon->temp.sensors)
|
|
return -ENOMEM;
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "temperature-") {
|
|
sensor = &hwmon->temp.sensors[hwmon->temp.count];
|
|
if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
|
|
sensor->attrs = HWMON_T_INPUT;
|
|
|
|
if (*sensor->label)
|
|
sensor->attrs |= HWMON_T_LABEL;
|
|
|
|
hwmon->temp.count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "voltage-") {
|
|
n_voltage++;
|
|
}
|
|
|
|
if (n_voltage) {
|
|
hwmon->volt.sensors = devm_kcalloc(hwmon->dev, n_voltage,
|
|
sizeof(struct macsmc_hwmon_sensor), GFP_KERNEL);
|
|
if (!hwmon->volt.sensors)
|
|
return -ENOMEM;
|
|
|
|
for_each_child_of_node_with_prefix(hwmon_node, key_node, "volt-") {
|
|
sensor = &hwmon->temp.sensors[hwmon->temp.count];
|
|
if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc, key_node, sensor)) {
|
|
sensor->attrs = HWMON_I_INPUT;
|
|
|
|
if (*sensor->label)
|
|
sensor->attrs |= HWMON_I_LABEL;
|
|
|
|
hwmon->volt.count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Create NULL-terminated config arrays */
|
|
static void macsmc_hwmon_populate_configs(u32 *configs, const struct macsmc_hwmon_sensors *sensors)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < sensors->count; idx++)
|
|
configs[idx] = sensors->sensors[idx].attrs;
|
|
}
|
|
|
|
static void macsmc_hwmon_populate_fan_configs(u32 *configs, const struct macsmc_hwmon_fans *fans)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < fans->count; idx++)
|
|
configs[idx] = fans->fans[idx].attrs;
|
|
}
|
|
|
|
static const struct hwmon_channel_info *const macsmc_chip_channel_info =
|
|
HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ);
|
|
|
|
static int macsmc_hwmon_create_infos(struct macsmc_hwmon *hwmon)
|
|
{
|
|
struct hwmon_channel_info *channel_info;
|
|
int i = 0;
|
|
|
|
/* chip */
|
|
hwmon->channel_infos[i++] = macsmc_chip_channel_info;
|
|
|
|
if (hwmon->curr.count) {
|
|
channel_info = &hwmon->curr.channel_info;
|
|
channel_info->type = hwmon_curr;
|
|
channel_info->config = devm_kcalloc(hwmon->dev, hwmon->curr.count + 1,
|
|
sizeof(u32), GFP_KERNEL);
|
|
if (!channel_info->config)
|
|
return -ENOMEM;
|
|
|
|
macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->curr);
|
|
hwmon->channel_infos[i++] = channel_info;
|
|
}
|
|
|
|
if (hwmon->fan.count) {
|
|
channel_info = &hwmon->fan.channel_info;
|
|
channel_info->type = hwmon_fan;
|
|
channel_info->config = devm_kcalloc(hwmon->dev, hwmon->fan.count + 1,
|
|
sizeof(u32), GFP_KERNEL);
|
|
if (!channel_info->config)
|
|
return -ENOMEM;
|
|
|
|
macsmc_hwmon_populate_fan_configs((u32 *)channel_info->config, &hwmon->fan);
|
|
hwmon->channel_infos[i++] = channel_info;
|
|
}
|
|
|
|
if (hwmon->power.count) {
|
|
channel_info = &hwmon->power.channel_info;
|
|
channel_info->type = hwmon_power;
|
|
channel_info->config = devm_kcalloc(hwmon->dev, hwmon->power.count + 1,
|
|
sizeof(u32), GFP_KERNEL);
|
|
if (!channel_info->config)
|
|
return -ENOMEM;
|
|
|
|
macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->power);
|
|
hwmon->channel_infos[i++] = channel_info;
|
|
}
|
|
|
|
if (hwmon->temp.count) {
|
|
channel_info = &hwmon->temp.channel_info;
|
|
channel_info->type = hwmon_temp;
|
|
channel_info->config = devm_kcalloc(hwmon->dev, hwmon->temp.count + 1,
|
|
sizeof(u32), GFP_KERNEL);
|
|
if (!channel_info->config)
|
|
return -ENOMEM;
|
|
|
|
macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->temp);
|
|
hwmon->channel_infos[i++] = channel_info;
|
|
}
|
|
|
|
if (hwmon->volt.count) {
|
|
channel_info = &hwmon->volt.channel_info;
|
|
channel_info->type = hwmon_in;
|
|
channel_info->config = devm_kcalloc(hwmon->dev, hwmon->volt.count + 1,
|
|
sizeof(u32), GFP_KERNEL);
|
|
if (!channel_info->config)
|
|
return -ENOMEM;
|
|
|
|
macsmc_hwmon_populate_configs((u32 *)channel_info->config, &hwmon->volt);
|
|
hwmon->channel_infos[i++] = channel_info;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int macsmc_hwmon_probe(struct platform_device *pdev)
|
|
{
|
|
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
|
|
struct macsmc_hwmon *hwmon;
|
|
int ret;
|
|
|
|
/*
|
|
* The MFD driver will try to probe us unconditionally. Some devices
|
|
* with the SMC do not have hwmon capabilities. Only probe if we have
|
|
* a hwmon node.
|
|
*/
|
|
if (!pdev->dev.of_node)
|
|
return -ENODEV;
|
|
|
|
hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon),
|
|
GFP_KERNEL);
|
|
if (!hwmon)
|
|
return -ENOMEM;
|
|
|
|
hwmon->dev = &pdev->dev;
|
|
hwmon->smc = smc;
|
|
|
|
ret = macsmc_hwmon_populate_sensors(hwmon, hwmon->dev->of_node);
|
|
if (ret) {
|
|
dev_err(hwmon->dev, "Could not parse sensors\n");
|
|
return ret;
|
|
}
|
|
|
|
if (!hwmon->curr.count && !hwmon->fan.count &&
|
|
!hwmon->power.count && !hwmon->temp.count &&
|
|
!hwmon->volt.count) {
|
|
dev_err(hwmon->dev,
|
|
"No valid sensors found of any supported type\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = macsmc_hwmon_create_infos(hwmon);
|
|
if (ret)
|
|
return ret;
|
|
|
|
hwmon->chip_info.ops = &macsmc_hwmon_ops;
|
|
hwmon->chip_info.info =
|
|
(const struct hwmon_channel_info *const *)&hwmon->channel_infos;
|
|
|
|
hwmon->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
|
|
"macsmc_hwmon", hwmon,
|
|
&hwmon->chip_info, NULL);
|
|
if (IS_ERR(hwmon->hwmon_dev))
|
|
return dev_err_probe(hwmon->dev, PTR_ERR(hwmon->hwmon_dev),
|
|
"Probing SMC hwmon device failed\n");
|
|
|
|
dev_dbg(hwmon->dev, "Registered SMC hwmon device. Sensors:\n");
|
|
dev_dbg(hwmon->dev,
|
|
"Current: %d, Fans: %d, Power: %d, Temperature: %d, Voltage: %d",
|
|
hwmon->curr.count, hwmon->fan.count,
|
|
hwmon->power.count, hwmon->temp.count,
|
|
hwmon->volt.count);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id macsmc_hwmon_of_table[] = {
|
|
{ .compatible = "apple,smc-hwmon" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, macsmc_hwmon_of_table);
|
|
|
|
static struct platform_driver macsmc_hwmon_driver = {
|
|
.probe = macsmc_hwmon_probe,
|
|
.driver = {
|
|
.name = "macsmc-hwmon",
|
|
.of_match_table = macsmc_hwmon_of_table,
|
|
},
|
|
};
|
|
module_platform_driver(macsmc_hwmon_driver);
|
|
|
|
MODULE_DESCRIPTION("Apple Silicon SMC hwmon driver");
|
|
MODULE_AUTHOR("James Calligeros <jcalligeros99@gmail.com>");
|
|
MODULE_LICENSE("Dual MIT/GPL");
|