From 6d1be33af986a10d63cf4c4dbd6a2cd7532d8bde Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 26 Jul 2019 04:47:27 +0200 Subject: [PATCH 12/12] surfacebook2-dgpu --- drivers/platform/x86/Kconfig | 9 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/surfacebook2_dgpu_hps.c | 306 +++++++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 drivers/platform/x86/surfacebook2_dgpu_hps.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 04421fe566ba..cb0a53da4de1 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -484,6 +484,15 @@ config SURFACE3_WMI To compile this driver as a module, choose M here: the module will be called surface3-wmi. +config SURFACE_BOOK2_DGPU_HPS + tristate "Surface Book 2 dGPU Hot-Plug System Driver" + depends on ACPI + ---help--- + This is an experimetnal driver to control the power-state of the + Surface Book 2 dGPU. + + If you have a Surface Book 2, say Y or M here. + config THINKPAD_ACPI tristate "ThinkPad ACPI Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 58b07217c3cf..d18d3dcc5749 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o +obj-$(CONFIG_SURFACE_BOOK2_DGPU_HPS) += surfacebook2_dgpu_hps.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o diff --git a/drivers/platform/x86/surfacebook2_dgpu_hps.c b/drivers/platform/x86/surfacebook2_dgpu_hps.c new file mode 100644 index 000000000000..7639fb0029d8 --- /dev/null +++ b/drivers/platform/x86/surfacebook2_dgpu_hps.c @@ -0,0 +1,306 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define SB2_SHPS_DSM_REVISION 1 +#define SB2_SHPS_DSM_GPU_STATE 0x05 + +static const guid_t SB2_SHPS_DSM_UUID = + GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, + 0x32, 0x0e, 0x10, 0x36, 0x0a); + +#define SB2_PARAM_PERM (S_IRUGO | S_IWUSR) + + +static const struct acpi_gpio_params gpio_base_presence_int = { 0, 0, false }; +static const struct acpi_gpio_params gpio_base_presence = { 1, 0, false }; +static const struct acpi_gpio_params gpio_dgpu_power_int = { 2, 0, false }; +static const struct acpi_gpio_params gpio_dgpu_power = { 3, 0, false }; +static const struct acpi_gpio_params gpio_dgpu_presence_int = { 4, 0, false }; +static const struct acpi_gpio_params gpio_dgpu_presence = { 5, 0, false }; + +static const struct acpi_gpio_mapping sb2_mshw0153_acpi_gpios[] = { + { "base_presence-int-gpio", &gpio_base_presence_int, 1 }, + { "base_presence-gpio", &gpio_base_presence, 1 }, + { "dgpu_power-int-gpio", &gpio_dgpu_power_int, 1 }, + { "dgpu_power-gpio", &gpio_dgpu_power, 1 }, + { "dgpu_presence-int-gpio", &gpio_dgpu_presence_int, 1 }, + { "dgpu_presence-gpio", &gpio_dgpu_presence, 1 }, + { }, +}; + + +enum sb2_dgpu_power { + SB2_DGPU_POWER_OFF = 0, + SB2_DGPU_POWER_ON = 1, + + __SB2_DGPU_POWER__START = 0, + __SB2_DGPU_POWER__END = 1, +}; + +enum sb2_param_dgpu_power { + SB2_PARAM_DGPU_POWER_OFF = SB2_DGPU_POWER_OFF, + SB2_PARAM_DGPU_POWER_ON = SB2_DGPU_POWER_ON, + SB2_PARAM_DGPU_POWER_AS_IS = 2, + + __SB2_PARAM_DGPU_POWER__START = 0, + __SB2_PARAM_DGPU_POWER__END = 2, +}; + +static const char* sb2_dgpu_power_str(enum sb2_dgpu_power power) { + if (power == SB2_DGPU_POWER_OFF) { + return "off"; + } else if (power == SB2_DGPU_POWER_ON) { + return "on"; + } else { + return ""; + } +} + + +struct sb2_shps_driver_data { + struct mutex dgpu_power_lock; + enum sb2_dgpu_power dgpu_power; +}; + + +static int __sb2_shps_dgpu_set_power(struct platform_device *pdev, enum sb2_dgpu_power power) +{ + struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + union acpi_object *result; + union acpi_object param; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = power == SB2_DGPU_POWER_ON; + + result = acpi_evaluate_dsm_typed(handle, &SB2_SHPS_DSM_UUID, SB2_SHPS_DSM_REVISION, + SB2_SHPS_DSM_GPU_STATE, ¶m, ACPI_TYPE_BUFFER); + + if (IS_ERR_OR_NULL(result)) { + return result ? PTR_ERR(result) : -EFAULT; + } + + if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { + return -EIO; + } + + drvdata->dgpu_power = power; + + printk(KERN_INFO "sb2_shps: dGPU power state set to \'%s\'\n", sb2_dgpu_power_str(power)); + + ACPI_FREE(result); + return 0; +} + +static int sb2_shps_dgpu_set_power(struct platform_device *pdev, enum sb2_dgpu_power power) +{ + struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); + int status = 0; + + if (power < __SB2_DGPU_POWER__START || power > __SB2_DGPU_POWER__END) { + return -EINVAL; + } + + mutex_lock(&drvdata->dgpu_power_lock); + if (power != drvdata->dgpu_power) { + status = __sb2_shps_dgpu_set_power(pdev, power); + } + mutex_unlock(&drvdata->dgpu_power_lock); + + return status; +} + +static int sb2_shps_dgpu_force_power(struct platform_device *pdev, enum sb2_dgpu_power power) +{ + struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); + int status; + + if (power < __SB2_DGPU_POWER__START || power > __SB2_DGPU_POWER__END) { + return -EINVAL; + } + + mutex_lock(&drvdata->dgpu_power_lock); + status = __sb2_shps_dgpu_set_power(pdev, power); + mutex_unlock(&drvdata->dgpu_power_lock); + + return status; +} + + +static int param_dgpu_power_set(const char *val, const struct kernel_param *kp) +{ + int power = SB2_PARAM_DGPU_POWER_OFF; + int status; + + status = kstrtoint(val, 0, &power); + if (status) { + return status; + } + + if (power < __SB2_PARAM_DGPU_POWER__START || power > __SB2_PARAM_DGPU_POWER__END) { + return -EINVAL; + } + + return param_set_int(val, kp); +} + +static const struct kernel_param_ops param_dgpu_power_ops = { + .set = param_dgpu_power_set, + .get = param_get_int, +}; + +static int param_dgpu_power_init = SB2_PARAM_DGPU_POWER_OFF; +static int param_dgpu_power_exit = SB2_PARAM_DGPU_POWER_OFF; + +module_param_cb(dgpu_power_init, ¶m_dgpu_power_ops, ¶m_dgpu_power_init, SB2_PARAM_PERM); +module_param_cb(dgpu_power_exit, ¶m_dgpu_power_ops, ¶m_dgpu_power_exit, SB2_PARAM_PERM); + +MODULE_PARM_DESC(dgpu_power_init, "dGPU power state to be set on init (0: off / 1: on / 2: as-is)"); +MODULE_PARM_DESC(dgpu_power_exit, "dGPU power state to be set on exit (0: off / 1: on / 2: as-is)"); + + +static ssize_t dgpu_power_show(struct device *dev, struct device_attribute *attr, char *data) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); + + return sprintf(data, "%s\n", sb2_dgpu_power_str(drvdata->dgpu_power)); +} + +static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, + const char *data, size_t count) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + bool power = false; + int status; + + status = kstrtobool(data, &power); + if (status) { + return status; + } + + if (power) { + status = sb2_shps_dgpu_set_power(pdev, SB2_DGPU_POWER_ON); + } else { + status = sb2_shps_dgpu_set_power(pdev, SB2_DGPU_POWER_OFF); + } + + return status < 0 ? status : count; +} + +const static DEVICE_ATTR_RW(dgpu_power); + + +#ifdef CONFIG_PM + +static int sb2_shps_resume(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); + + return sb2_shps_dgpu_force_power(pdev, drvdata->dgpu_power); +} + +static SIMPLE_DEV_PM_OPS(sb2_shps_pm_ops, NULL, sb2_shps_resume); + +#endif + + +static int sb2_shps_probe(struct platform_device *pdev) +{ + struct sb2_shps_driver_data *drvdata; + struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); + int status = 0; + + if (gpiod_count(&pdev->dev, NULL) < 0) { + return -ENODEV; + } + + status = acpi_dev_add_driver_gpios(shps_dev, sb2_mshw0153_acpi_gpios); + if (status) { + return status; + } + + drvdata = kzalloc(sizeof(struct sb2_shps_driver_data), GFP_KERNEL); + if (!drvdata) { + status = -ENOMEM; + goto err_alloc_drvdata; + } + + mutex_init(&drvdata->dgpu_power_lock); + drvdata->dgpu_power = SB2_DGPU_POWER_OFF; + platform_set_drvdata(pdev, drvdata); + + if (param_dgpu_power_init != SB2_PARAM_DGPU_POWER_AS_IS) { + status = sb2_shps_dgpu_force_power(pdev, param_dgpu_power_init); + if (status) { + goto err_set_power; + } + } + + status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_dgpu_power.attr); + if (status) { + goto err_sysfs; + } + + return 0; + +err_sysfs: + sb2_shps_dgpu_force_power(pdev, SB2_DGPU_POWER_OFF); +err_set_power: + platform_set_drvdata(pdev, NULL); + kfree(drvdata); +err_alloc_drvdata: + acpi_dev_remove_driver_gpios(shps_dev); + return status; +} + +static int sb2_shps_remove(struct platform_device *pdev) +{ + struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); + struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); + + sysfs_remove_file(&pdev->dev.kobj, &dev_attr_dgpu_power.attr); + + if (param_dgpu_power_exit != SB2_PARAM_DGPU_POWER_AS_IS) { + sb2_shps_dgpu_set_power(pdev, param_dgpu_power_exit); + } + acpi_dev_remove_driver_gpios(shps_dev); + + platform_set_drvdata(pdev, NULL); + kfree(drvdata); + + return 0; +} + + +static const struct acpi_device_id sb2_shps_acpi_match[] = { + { "MSHW0153", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, sb2_shps_acpi_match); + +static struct platform_driver sb2_shps_driver = { + .probe = sb2_shps_probe, + .remove = sb2_shps_remove, + .driver = { + .name = "sb2_shps", + .acpi_match_table = ACPI_PTR(sb2_shps_acpi_match), +#ifdef CONFIG_PM + .pm = &sb2_shps_pm_ops, +#endif + }, +}; +module_platform_driver(sb2_shps_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Book 2 Hot-Plug System Driver"); +MODULE_LICENSE("GPL v2"); -- 2.23.0