From 688370cf19ee6b29ea39dce2783ae352fb0a9306 Mon Sep 17 00:00:00 2001
From: Andrey Smirnov <andrew.smirnov@gmail.com>
Date: Sat, 19 Feb 2022 16:08:36 -0800
Subject: [PATCH 04/21] mfd: Add MFD core driver for Steam Deck

Add MFD core driver for Steam Deck. Doesn't really do much so far
besides instantiating a number of MFD cells that implement all the
interesting functionality.

(cherry picked from commit 5f534c2d6ebdefccb9c024eb0f013bc1c0c622d9)
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
 drivers/mfd/Kconfig     |  11 ++++
 drivers/mfd/Makefile    |   2 +
 drivers/mfd/steamdeck.c | 127 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+)
 create mode 100644 drivers/mfd/steamdeck.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index ae23b317a64e..7417c480275a 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2414,5 +2414,16 @@ config MFD_RSMU_SPI
 	  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
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e057d6d6faef..2f9f8a0daf5e 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -290,3 +290,5 @@ obj-$(CONFIG_MFD_ATC260X_I2C)	+= atc260x-i2c.o
 
 obj-$(CONFIG_MFD_RSMU_I2C)	+= rsmu_i2c.o rsmu_core.o
 obj-$(CONFIG_MFD_RSMU_SPI)	+= rsmu_spi.o rsmu_core.o
+
+obj-$(CONFIG_MFD_STEAMDECK)	+= steamdeck.o
diff --git a/drivers/mfd/steamdeck.c b/drivers/mfd/steamdeck.c
new file mode 100644
index 000000000000..0e504b3c2796
--- /dev/null
+++ b/drivers/mfd/steamdeck.c
@@ -0,0 +1,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");
-- 
2.48.1

