438 changes: 438 additions & 0 deletions drivers/hwmon/apple_soc_smc.c
@@ -0,0 +1,438 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*
* Apple SoC SMC Hw Monitoring module
* Copyright The Asahi Linux Contributors
*/

#include <linux/ctype.h>
#include <linux/minmax.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
#include <linux/mfd/macsmc.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>

struct macsmc_hwmon {
struct device *dev;
struct apple_smc *smc;
struct device *hwmon_dev;
struct channel_info *power_table;
struct channel_info *temp_table;
struct channel_info *voltage_table;
struct channel_info *current_table;
u32 power_keys_cnt, temp_keys_cnt, voltage_keys_cnt, current_keys_cnt;
};

static u32 apple_soc_smc_float_to_int(u32 flt)
{
unsigned int sign, exp, mant;
unsigned long val;
int i, b;
s32 result;

sign = flt>>31;
exp = flt>>23;
mant = flt<<9>>9;

result = 0;
val = 0;
if (exp == 0 && mant != 0) {
for (i = 22; i >= 0; i -= 1) {
b = (mant&(1<<i))>>i;
val += b*(1000000000>>(23-i));
}
if (exp > 127)
result = (val<<(exp-127))/1000000;
else
result = (val>>(127-exp))/1000000;
} else if (!(exp == 0 && mant == 0)) {
for (i = 22; i >= 0; i -= 1) {
b = (mant&(1<<i))>>i;
val += b*(1000000000>>(23-i));
}
if (exp > 127)
result = ((val+1000000000)<<(exp-127))/1000000;
else
result = ((val+1000000000)>>(127-exp))/1000000;
}

if (sign == 1)
result *= -1;

return result;
}


#define MAX_LABEL_LEN 30

struct channel_info {
u32 smc_key;
char label[MAX_LABEL_LEN];
};


static int apple_soc_smc_read_labels(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:
if (channel < hwmon->temp_keys_cnt)
*str = hwmon->temp_table[channel].label;
else
return -EOPNOTSUPP;
break;
case hwmon_curr:
if (channel < hwmon->current_keys_cnt)
*str = hwmon->current_table[channel].label;
else
return -EOPNOTSUPP;
break;
case hwmon_in:
if (channel < hwmon->voltage_keys_cnt)
*str = hwmon->voltage_table[channel].label;
else
return -EOPNOTSUPP;
break;
case hwmon_power:
if (channel < hwmon->power_keys_cnt)
*str = hwmon->power_table[channel].label;
else
return -EOPNOTSUPP;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}

static int apple_soc_smc_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
struct apple_smc *smc = hwmon->smc;
struct apple_smc_key_info key_info;

int ret = 0;
u32 vu32 = 0;
u8 vu8 = 0;

switch (type) {
case hwmon_temp:
if ((channel < hwmon->temp_keys_cnt) && (hwmon->temp_table[channel].smc_key != 0)) {
ret = apple_smc_read_u32(smc, hwmon->temp_table[channel].smc_key, &vu32);
if (ret == 0)
*val = apple_soc_smc_float_to_int(vu32);
} else
ret = -EOPNOTSUPP;
break;

case hwmon_curr:
if ((channel < hwmon->current_keys_cnt) && (hwmon->current_table[channel].smc_key != 0)) {
ret = apple_smc_read_u32(smc, hwmon->current_table[channel].smc_key, &vu32);
if (ret == 0)
*val = apple_soc_smc_float_to_int(vu32);
} else
ret = -EOPNOTSUPP;
break;

case hwmon_in:
if ((channel < hwmon->voltage_keys_cnt) && (hwmon->voltage_table[channel].smc_key != 0)) {
ret = apple_smc_read_u32(smc, hwmon->voltage_table[channel].smc_key, &vu32);
if (ret == 0)
*val = apple_soc_smc_float_to_int(vu32);
} else
ret = -EOPNOTSUPP;
break;

case hwmon_power:
if ((channel < hwmon->power_keys_cnt) && (hwmon->power_table[channel].smc_key != 0)) {
ret = apple_smc_get_key_info(smc, hwmon->power_table[channel].smc_key, &key_info);
if (ret == 0) {
if (key_info.type_code == 0x75693820) {
ret = apple_smc_read_u8(smc, hwmon->power_table[channel].smc_key, &vu8);
if (ret == 0)
*val = vu8;
} else if (key_info.type_code == 0x666C7420) {
ret = apple_smc_read_u32(smc, hwmon->power_table[channel].smc_key, &vu32);
if (ret == 0)
*val = apple_soc_smc_float_to_int(vu32);
} else
ret = -EOPNOTSUPP;
} else
dev_err(dev, "Got an error on apple_smc_get_key_info\n");
} else
ret = -EOPNOTSUPP;
break;

default:
ret = -EOPNOTSUPP;
}
return ret;
}


static int apple_soc_smc_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
return -EOPNOTSUPP;
}

static umode_t apple_soc_smc_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
umode_t mode = 0444;
return mode;
}


static const struct hwmon_ops apple_soc_smc_hwmon_ops = {
.is_visible = apple_soc_smc_is_visible,
.read = apple_soc_smc_read,
.read_string = apple_soc_smc_read_labels,
.write = apple_soc_smc_write,
};

static struct hwmon_chip_info apple_soc_smc_chip_info = {
.ops = &apple_soc_smc_hwmon_ops,
.info = NULL,
};

static int fill_key_table(struct platform_device *pdev, char *keys_node_name, u32 *keys_cnt, struct channel_info **chan_table)
{
struct device_node *sensors_node = NULL;
struct device_node *keys_np = NULL;
struct device_node *key_desc = NULL;
const char *label, *key_str;
int i, ret;


/* Set default values */
*keys_cnt = 0;
*chan_table = NULL;

sensors_node = of_find_node_by_name(NULL, "hwmon-sensors");
if (!sensors_node) {
dev_err(&pdev->dev, "No hwmon-sensors entry in device tree\n");
return 0;
}

keys_np = of_find_node_by_name(sensors_node, keys_node_name);
if (!keys_np) {
dev_err(&pdev->dev, "No %s entry in device tree\n", keys_node_name);
return 0;
}

*keys_cnt = of_get_child_count(keys_np);

dev_info(&pdev->dev, "Found %d SMC keys for %s\n", *keys_cnt, keys_node_name);

if (*keys_cnt > 0) {
*chan_table = devm_kzalloc(&pdev->dev, sizeof(struct channel_info)*(*keys_cnt), GFP_KERNEL);
if (!(*chan_table)) {
of_node_put(keys_np);
return -ENOMEM;
}

i = 0;
for_each_child_of_node(keys_np, key_desc) {

dev_info(&pdev->dev, "Node child loop: %s\n", of_node_full_name(key_desc));

ret = of_property_read_string(key_desc, "key", &key_str);
if (ret != 0)
dev_err(&pdev->dev, "Missing key property for %s\n", of_node_full_name(key_desc));
else {
(*chan_table)[i].smc_key = _SMC_KEY(key_str);
dev_info(&pdev->dev, "Found SMC key: %s\n", key_str);
}

ret = of_property_read_string(key_desc, "label", &label);
if (ret != 0) {
dev_err(&pdev->dev, "Missing label property for %s. Using node name.\n", of_node_full_name(key_desc));
strncpy((*chan_table)[i].label, of_node_full_name(key_desc), min(strlen(of_node_full_name(key_desc)), (size_t)MAX_LABEL_LEN-1));
} else {
strncpy((*chan_table)[i].label, label, min(strlen(label), (size_t)MAX_LABEL_LEN-1));
dev_info(&pdev->dev, "Found label: %s\n", label);
}

i += 1;
}
}
of_node_put(keys_np);
return 0;
}

static void fill_config_entries(u32 *config, u32 flags, u32 cnt)
{
int i;

for (i = 0; i < cnt; i += 1)
config[i] = flags;

config[i] = 0;
}

static int apple_soc_smc_hwmon_probe(struct platform_device *pdev)
{
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct macsmc_hwmon *smc_hwmon;
struct hwmon_channel_info **apple_soc_smc_info;
int index, offset;

if (!smc) {
dev_err(&pdev->dev, "No smc device pointer from parent device (check device tree)\n");
return -ENODEV;
}

smc_hwmon = devm_kzalloc(&pdev->dev, sizeof(*smc_hwmon), GFP_KERNEL);
if (!smc_hwmon)
return -ENOMEM;

smc_hwmon->dev = &pdev->dev;
smc_hwmon->smc = smc;

if (fill_key_table(pdev, "pwr_keys", &smc_hwmon->power_keys_cnt, &smc_hwmon->power_table) != 0) {
dev_err(&pdev->dev, "Failed to get pwr_keys property\n");
return -ENODEV;
}

if (fill_key_table(pdev, "temp_keys", &smc_hwmon->temp_keys_cnt, &smc_hwmon->temp_table) != 0) {
dev_err(&pdev->dev, "Failed to get temp_keys property\n");
return -ENODEV;
}

if (fill_key_table(pdev, "voltage_keys", &smc_hwmon->voltage_keys_cnt, &smc_hwmon->voltage_table) != 0) {
dev_err(&pdev->dev, "Failed to get voltage_keys property\n");
return -ENODEV;
}

if (fill_key_table(pdev, "current_keys", &smc_hwmon->current_keys_cnt, &smc_hwmon->current_table) != 0) {
dev_err(&pdev->dev, "Failed to get current_keys property\n");
return -ENODEV;
}

/* Building the channel infos needed: 1 chip, then temp, input(voltage), current, power
* The chip info is a list of null terminated pointer to channel_info struct, so we need 5 pointers for chip, temp, input,
* current and power plus a null termination, so a total of 6. This number could be a parameter coming from the DT.
* Then, we need one channel info struct per type, so another 5 channel_info struct, and then we need the u32 config
* entries for each type of channel, also null terminated: 1 config for chip plus a null entry, 40 entries for power plus a
* null entry, and so on.
*/


apple_soc_smc_info = devm_kzalloc(&pdev->dev, 3*sizeof(struct hwmon_channel_info *) + sizeof(struct hwmon_channel_info)*(1+1)+sizeof(u32)*(2+smc_hwmon->power_keys_cnt+1+smc_hwmon->voltage_keys_cnt+1+smc_hwmon->current_keys_cnt+1+smc_hwmon->temp_keys_cnt+1), GFP_KERNEL);
if (!apple_soc_smc_info)
return -ENOMEM;

/* Filling Chip info entries (type and config)*/
/* Set the chip info entry to point after the channel info pointer list and the null entry */
apple_soc_smc_info[0] = (struct hwmon_channel_info *)(apple_soc_smc_info + 6); // 1 for chip info, 1 for power info, 1 for voltage, 1 for current, 1 for temp and 1 null entry so + 6

/* Set the channel info type and pointer to the config list just after */
apple_soc_smc_info[0]->type = hwmon_chip;
apple_soc_smc_info[0]->config = (u32 *)(apple_soc_smc_info[0] + 1);

fill_config_entries((u32 *)apple_soc_smc_info[0]->config, HWMON_C_REGISTER_TZ, 1);

index = 1;
offset = 3;

if (smc_hwmon->power_keys_cnt > 0) {
/* Filling Power sensors info entries (channel and configs)*/
/* Set the next channel info pointer just after the previous entry ie config + 3 in this case */
apple_soc_smc_info[index] = (struct hwmon_channel_info *)(apple_soc_smc_info[index-1]->config + offset);

/* Set the channel info type and pointer to the config list just after */
apple_soc_smc_info[index]->type = hwmon_power;
apple_soc_smc_info[index]->config = (u32 *)(apple_soc_smc_info[index] + 1);

fill_config_entries((u32 *)apple_soc_smc_info[index]->config, HWMON_P_INPUT|HWMON_P_LABEL, smc_hwmon->power_keys_cnt);

index += 1;
offset = smc_hwmon->power_keys_cnt+1+1;
}

if (smc_hwmon->voltage_keys_cnt > 0) {
/* Filling Voltage(Input) sensors info entries (channel and config)*/
/* Set the next channel info pointer just after the previous entry ie config + smc_hwmon->power_keys_cnt + 1 for null +1 in this case */
apple_soc_smc_info[index] = (struct hwmon_channel_info *)(apple_soc_smc_info[index-1]->config + offset);

/* Set the channel info type and pointer to the config list just after */
apple_soc_smc_info[index]->type = hwmon_in;
apple_soc_smc_info[index]->config = (u32 *)(apple_soc_smc_info[index] + 1);

fill_config_entries((u32 *)apple_soc_smc_info[index]->config, HWMON_I_INPUT|HWMON_I_LABEL, smc_hwmon->voltage_keys_cnt);

index += 1;
offset = smc_hwmon->voltage_keys_cnt+1+1;
}

if (smc_hwmon->current_keys_cnt > 0) {
/* Filling Current sensors info entries (channel and config)*/
/* Set the next channel info pointer just after the previous entry ie config + smc_hwmon->voltage_keys_cnt + 1 for null +1 in this case */
apple_soc_smc_info[index] = (struct hwmon_channel_info *)(apple_soc_smc_info[index-1]->config + offset);

/* Set the channel info type and pointer to the config list just after */
apple_soc_smc_info[index]->type = hwmon_curr;
apple_soc_smc_info[index]->config = (u32 *)(apple_soc_smc_info[index] + 1);

fill_config_entries((u32 *)apple_soc_smc_info[index]->config, HWMON_C_INPUT|HWMON_C_LABEL, smc_hwmon->current_keys_cnt);

index += 1;
offset = smc_hwmon->current_keys_cnt+1+1;
}

if (smc_hwmon->temp_keys_cnt > 0) {
/* Filling Temp sensors info entries (channel and config)*/
/* Set the next channel info pointer just after the previous entry ie config + smc_hwmon->voltage_keys_cnt + 1 for null +1 in this case */
apple_soc_smc_info[index] = (struct hwmon_channel_info *)(apple_soc_smc_info[index-1]->config + offset);

/* Set the channel info type and pointer to the config list just after */
apple_soc_smc_info[index]->type = hwmon_temp;
apple_soc_smc_info[index]->config = (u32 *)(apple_soc_smc_info[index] + 1);

fill_config_entries((u32 *)apple_soc_smc_info[index]->config, HWMON_T_INPUT|HWMON_T_LABEL, smc_hwmon->temp_keys_cnt);

index += 1;
}

/* This is a null terminated pointer list, so last entry must be set to NULL */
apple_soc_smc_info[index] = NULL;

/* This table must be set as the chip info entry to be register */
apple_soc_smc_chip_info.info = (const struct hwmon_channel_info **)apple_soc_smc_info;

smc_hwmon->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
"apple_soc_smc_hwmon", smc_hwmon,
&apple_soc_smc_chip_info, NULL);
if (IS_ERR(smc_hwmon->hwmon_dev))
return dev_err_probe(dev, PTR_ERR(smc_hwmon->hwmon_dev),
"failed to register hwmon device\n");

return 0;
}


static struct platform_driver apple_soc_smc_hwmon_driver = {
.probe = apple_soc_smc_hwmon_probe,
.driver = {
.name = "macsmc-hwmon",
.owner = THIS_MODULE,
},
};

module_platform_driver(apple_soc_smc_hwmon_driver);

MODULE_DESCRIPTION("Apple SoC SMC Hwmon driver");
MODULE_AUTHOR("Jean-Francois Bortolotti <jeff@borto.fr>");
MODULE_LICENSE("GPL");

3 changes: 3 additions & 0 deletions drivers/platform/apple/smc_core.c
Expand Up @@ -33,6 +33,9 @@ static const struct mfd_cell apple_smc_devs[] = {
{
.name = "macsmc-hid",
},
{
.name = "macsmc-hwmon",
},
{
.name = "macsmc-power",
},
Expand Down
2 changes: 1 addition & 1 deletion drivers/platform/apple/smc_rtkit.c
Expand Up @@ -224,7 +224,7 @@ static int apple_smc_rtkit_get_key_info(void *cookie, smc_key key, struct apple_

ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_INFO, key, 6, 0, NULL);
if (ret >= 0 && info) {
memcpy_fromio(key_info,smc->shmem.iomem,6);
memcpy_fromio(key_info, smc->shmem.iomem, 6);
info->size = key_info[0];
info->type_code = get_unaligned_be32(&key_info[1]);
info->flags = key_info[5];
Expand Down