Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions drivers/hwmon/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2160,6 +2160,17 @@ config SENSORS_SCH5636
This driver can also be built as a module. If so, the module
will be called sch5636.

config SENSORS_STEAMDECK
tristate "Steam Deck EC sensors"
depends on MFD_STEAMDECK
help
If you say yes here you get support for the hardware
monitoring features exposed by EC firmware on Steam Deck
devices

This driver can also be built as a module. If so, the module
will be called steamdeck-hwmon.

config SENSORS_STTS751
tristate "ST Microelectronics STTS751"
depends on I2C
Expand Down
1 change: 1 addition & 0 deletions drivers/hwmon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o
obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o
obj-$(CONFIG_SENSORS_STEAMDECK) += steamdeck-hwmon.o
obj-$(CONFIG_SENSORS_STTS751) += stts751.o
obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o
obj-$(CONFIG_SENSORS_SURFACE_TEMP)+= surface_temp.o
Expand Down
294 changes: 294 additions & 0 deletions drivers/hwmon/steamdeck-hwmon.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Steam Deck EC sensors driver
*
* Copyright (C) 2021-2022 Valve Corporation
*/

#include <linux/acpi.h>
#include <linux/hwmon.h>
#include <linux/platform_device.h>

#define STEAMDECK_HWMON_NAME "steamdeck-hwmon"

struct steamdeck_hwmon {
struct acpi_device *adev;
};

static long
steamdeck_hwmon_get(struct steamdeck_hwmon *sd, const char *method)
{
unsigned long long val;
if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle,
(char *)method, NULL, &val)))
return -EIO;

return val;
}

static int
steamdeck_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *out)
{
struct steamdeck_hwmon *sd = dev_get_drvdata(dev);

switch (type) {
case hwmon_curr:
if (attr != hwmon_curr_input)
return -EOPNOTSUPP;

*out = steamdeck_hwmon_get(sd, "PDAM");
if (*out < 0)
return *out;
break;
case hwmon_in:
if (attr != hwmon_in_input)
return -EOPNOTSUPP;

*out = steamdeck_hwmon_get(sd, "PDVL");
if (*out < 0)
return *out;
break;
case hwmon_temp:
if (attr != hwmon_temp_input)
return -EOPNOTSUPP;

*out = steamdeck_hwmon_get(sd, "BATT");
if (*out < 0)
return *out;
/*
* Assuming BATT returns deg C we need to mutiply it
* by 1000 to convert to mC
*/
*out *= 1000;
break;
case hwmon_fan:
switch (attr) {
case hwmon_fan_input:
*out = steamdeck_hwmon_get(sd, "FANR");
if (*out < 0)
return *out;
break;
case hwmon_fan_target:
*out = steamdeck_hwmon_get(sd, "FSSR");
if (*out < 0)
return *out;
break;
case hwmon_fan_fault:
*out = steamdeck_hwmon_get(sd, "FANC");
if (*out < 0)
return *out;
/*
* FANC (Fan check):
* 0: Abnormal
* 1: Normal
*/
*out = !*out;
break;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}

return 0;
}

static int
steamdeck_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
switch (type) {
/*
* These two aren't, strictly speaking, measured. EC
* firmware just reports what PD negotiation resulted
* in.
*/
case hwmon_curr:
*str = "PD Contract Current";
break;
case hwmon_in:
*str = "PD Contract Voltage";
break;
case hwmon_temp:
*str = "Battery Temp";
break;
case hwmon_fan:
*str = "System Fan";
break;
default:
return -EOPNOTSUPP;
}

return 0;
}

static int
steamdeck_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct steamdeck_hwmon *sd = dev_get_drvdata(dev);

if (type != hwmon_fan ||
attr != hwmon_fan_target)
return -EOPNOTSUPP;

val = clamp_val(val, 0, 7300);

if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
"FANS", val)))
return -EIO;

return 0;
}

static umode_t
steamdeck_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
if (type == hwmon_fan &&
attr == hwmon_fan_target)
return 0644;

return 0444;
}

static const struct hwmon_channel_info *steamdeck_hwmon_info[] = {
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_LABEL),
HWMON_CHANNEL_INFO(curr,
HWMON_C_INPUT | HWMON_C_LABEL),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL |
HWMON_F_TARGET | HWMON_F_FAULT),
NULL
};

static const struct hwmon_ops steamdeck_hwmon_ops = {
.is_visible = steamdeck_hwmon_is_visible,
.read = steamdeck_hwmon_read,
.read_string = steamdeck_hwmon_read_string,
.write = steamdeck_hwmon_write,
};

static const struct hwmon_chip_info steamdeck_hwmon_chip_info = {
.ops = &steamdeck_hwmon_ops,
.info = steamdeck_hwmon_info,
};


static ssize_t
steamdeck_hwmon_simple_store(struct device *dev, const char *buf, size_t count,
const char *method,
unsigned long upper_limit)
{
struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
unsigned long value;

if (kstrtoul(buf, 10, &value) || value >= upper_limit)
return -EINVAL;

if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
(char *)method, value)))
return -EIO;

return count;
}

static ssize_t
steamdeck_hwmon_simple_show(struct device *dev, char *buf,
const char *method)
{
struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
unsigned long value;

value = steamdeck_hwmon_get(sd, method);
if (value < 0)
return value;

return sprintf(buf, "%ld\n", value);
}

#define STEAMDECK_HWMON_ATTR_RW(_name, _set_method, _get_method, \
_upper_limit) \
static ssize_t _name##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
return steamdeck_hwmon_simple_show(dev, buf, \
_get_method); \
} \
static ssize_t _name##_store(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t count) \
{ \
return steamdeck_hwmon_simple_store(dev, buf, count, \
_set_method, \
_upper_limit); \
} \
static DEVICE_ATTR_RW(_name)

STEAMDECK_HWMON_ATTR_RW(max_battery_charge_level, "FCBL", "SFBL", 101);
STEAMDECK_HWMON_ATTR_RW(max_battery_charge_rate, "CHGR", "GCHR", 101);

static struct attribute *steamdeck_hwmon_attributes[] = {
&dev_attr_max_battery_charge_level.attr,
&dev_attr_max_battery_charge_rate.attr,
NULL
};

static const struct attribute_group steamdeck_hwmon_group = {
.attrs = steamdeck_hwmon_attributes,
};

static const struct attribute_group *steamdeck_hwmon_groups[] = {
&steamdeck_hwmon_group,
NULL
};

static int steamdeck_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct steamdeck_hwmon *sd;
struct device *hwmon;

sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
if (!sd)
return -ENOMEM;

sd->adev = ACPI_COMPANION(dev->parent);
hwmon = devm_hwmon_device_register_with_info(dev,
"steamdeck_hwmon",
sd,
&steamdeck_hwmon_chip_info,
steamdeck_hwmon_groups);
if (IS_ERR(hwmon)) {
dev_err(dev, "Failed to register HWMON device");
return PTR_ERR(hwmon);
}

return 0;
}

static const struct platform_device_id steamdeck_hwmon_id_table[] = {
{ .name = STEAMDECK_HWMON_NAME },
{}
};
MODULE_DEVICE_TABLE(platform, steamdeck_hwmon_id_table);

static struct platform_driver steamdeck_hwmon_driver = {
.probe = steamdeck_hwmon_probe,
.driver = {
.name = STEAMDECK_HWMON_NAME,
},
.id_table = steamdeck_hwmon_id_table,
};
module_platform_driver(steamdeck_hwmon_driver);

MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
MODULE_DESCRIPTION("Steam Deck EC sensors driver");
MODULE_LICENSE("GPL");
11 changes: 11 additions & 0 deletions drivers/mfd/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2580,5 +2580,16 @@ config MFD_MAX7360
additional drivers must be enabled in order to use the functionality
of the device.

config MFD_STEAMDECK
tristate "Valve Steam Deck"
select MFD_CORE
depends on ACPI
depends on X86_64 || COMPILE_TEST
help
This driver registers various MFD cells that expose aspects
of Steam Deck specific ACPI functionality.

Say N here, unless you are running on Steam Deck hardware.

endmenu
endif
2 changes: 2 additions & 0 deletions drivers/mfd/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,5 @@ obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o
obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o

obj-$(CONFIG_MFD_LOONGSON_SE) += loongson-se.o

obj-$(CONFIG_MFD_STEAMDECK) += steamdeck.o
Loading