summary refs log tree commit diff
path: root/drivers/mfd/steamdeck.c
blob: 0e504b3c2796e0ce84b8fdd00fdaf608ccd74895 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// SPDX-License-Identifier: GPL-2.0+

/*
 * Steam Deck EC MFD core driver
 *
 * Copyright (C) 2021-2022 Valve Corporation
 *
 */

#include <linux/acpi.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>

#define STEAMDECK_STA_OK			\
	(ACPI_STA_DEVICE_ENABLED |		\
	 ACPI_STA_DEVICE_PRESENT |		\
	 ACPI_STA_DEVICE_FUNCTIONING)

struct steamdeck {
	struct acpi_device *adev;
	struct device *dev;
};

#define STEAMDECK_ATTR_RO(_name, _method)				\
	static ssize_t _name##_show(struct device *dev,			\
				    struct device_attribute *attr,	\
				    char *buf)				\
	{								\
		struct steamdeck *sd = dev_get_drvdata(dev);		\
		unsigned long long val;					\
									\
		if (ACPI_FAILURE(acpi_evaluate_integer(			\
					 sd->adev->handle,		\
					 _method, NULL, &val)))		\
			return -EIO;					\
									\
		return sysfs_emit(buf, "%llu\n", val);			\
	}								\
	static DEVICE_ATTR_RO(_name)

STEAMDECK_ATTR_RO(firmware_version, "PDFW");
STEAMDECK_ATTR_RO(board_id, "BOID");

static struct attribute *steamdeck_attrs[] = {
	&dev_attr_firmware_version.attr,
	&dev_attr_board_id.attr,
	NULL
};

ATTRIBUTE_GROUPS(steamdeck);

static const struct mfd_cell steamdeck_cells[] = {
	{ .name = "steamdeck-hwmon"  },
	{ .name = "steamdeck-leds"   },
	{ .name = "steamdeck-extcon" },
};

static void steamdeck_remove_sysfs_groups(void *data)
{
	struct steamdeck *sd = data;

	sysfs_remove_groups(&sd->dev->kobj, steamdeck_groups);
}

static int steamdeck_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	unsigned long long sta;
	struct steamdeck *sd;
	acpi_status status;
	int ret;

	sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
	if (!sd)
		return -ENOMEM;
	sd->adev = ACPI_COMPANION(dev);
	sd->dev = dev;
	platform_set_drvdata(pdev, sd);

	status = acpi_evaluate_integer(sd->adev->handle, "_STA",
				       NULL, &sta);
	if (ACPI_FAILURE(status)) {
		dev_err(dev, "Status check failed (0x%x)\n", status);
		return -EINVAL;
	}

	if ((sta & STEAMDECK_STA_OK) != STEAMDECK_STA_OK) {
		dev_err(dev, "Device is not ready\n");
		return -EINVAL;
	}

	ret = sysfs_create_groups(&dev->kobj, steamdeck_groups);
	if (ret) {
		dev_err(dev, "Failed to create sysfs group\n");
		return ret;
	}

	ret = devm_add_action_or_reset(dev, steamdeck_remove_sysfs_groups,
				       sd);
	if (ret) {
		dev_err(dev, "Failed to register devres action\n");
		return ret;
	}

	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
				    steamdeck_cells, ARRAY_SIZE(steamdeck_cells),
				    NULL, 0, NULL);
}

static const struct acpi_device_id steamdeck_device_ids[] = {
	{ "VLV0100", 0 },
	{ "", 0 },
};
MODULE_DEVICE_TABLE(acpi, steamdeck_device_ids);

static struct platform_driver steamdeck_driver = {
	.probe = steamdeck_probe,
	.driver = {
		.name = "steamdeck",
		.acpi_match_table = steamdeck_device_ids,
	},
};
module_platform_driver(steamdeck_driver);

MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
MODULE_DESCRIPTION("Steam Deck EC MFD core driver");
MODULE_LICENSE("GPL");