From 2fb7e9ae91350098db9a280277f424272816a9ab Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Tue, 28 Jan 2020 23:40:18 +0100 Subject: [PATCH] Add patches for v5.5 --- patches/5.5/0001-surface3-power.patch | 655 +++ patches/5.5/0002-surface3-spi.patch | 63 + patches/5.5/0003-surface3-oemb.patch | 69 + patches/5.5/0004-surface-sam.patch | 7435 +++++++++++++++++++++++++ patches/5.5/0005-surface-lte.patch | 24 + patches/5.5/0006-wifi.patch | 168 + patches/5.5/0007-ipts.patch | 1890 +++++++ 7 files changed, 10304 insertions(+) create mode 100644 patches/5.5/0001-surface3-power.patch create mode 100644 patches/5.5/0002-surface3-spi.patch create mode 100644 patches/5.5/0003-surface3-oemb.patch create mode 100644 patches/5.5/0004-surface-sam.patch create mode 100644 patches/5.5/0005-surface-lte.patch create mode 100644 patches/5.5/0006-wifi.patch create mode 100644 patches/5.5/0007-ipts.patch diff --git a/patches/5.5/0001-surface3-power.patch b/patches/5.5/0001-surface3-power.patch new file mode 100644 index 0000000000..f49951c711 --- /dev/null +++ b/patches/5.5/0001-surface3-power.patch @@ -0,0 +1,655 @@ +From a5d5a3b6e0eec527c9ce83b62450161e88232c5a Mon Sep 17 00:00:00 2001 +From: qzed +Date: Tue, 17 Sep 2019 17:17:56 +0200 +Subject: [PATCH 1/7] surface3-power + +--- + drivers/platform/x86/Kconfig | 7 + + drivers/platform/x86/Makefile | 1 + + drivers/platform/x86/surface3_power.c | 604 ++++++++++++++++++++++++++ + 3 files changed, 612 insertions(+) + create mode 100644 drivers/platform/x86/surface3_power.c + +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 27d5b40fb717..0e389c2e2101 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -1212,6 +1212,13 @@ config SURFACE_3_BUTTON + ---help--- + This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. + ++config SURFACE_3_POWER_OPREGION ++ tristate "Surface 3 battery platform operation region support" ++ depends on ACPI && I2C ++ help ++ Select this option to enable support for ACPI operation ++ region of the Surface 3 battery platform driver. ++ + config INTEL_PUNIT_IPC + tristate "Intel P-Unit IPC Driver" + ---help--- +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index 42d85a00be4e..d707a8edd738 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -89,6 +89,7 @@ obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o + obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o + obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o + obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o ++obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o + obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o + obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o + obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ +diff --git a/drivers/platform/x86/surface3_power.c b/drivers/platform/x86/surface3_power.c +new file mode 100644 +index 000000000000..e0af01a60302 +--- /dev/null ++++ b/drivers/platform/x86/surface3_power.c +@@ -0,0 +1,604 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++/* ++ * Supports for the power IC on the Surface 3 tablet. ++ * ++ * (C) Copyright 2016-2018 Red Hat, Inc ++ * (C) Copyright 2016-2018 Benjamin Tissoires ++ * (C) Copyright 2016 Stephen Just ++ * ++ */ ++ ++/* ++ * This driver has been reverse-engineered by parsing the DSDT of the Surface 3 ++ * and looking at the registers of the chips. ++ * ++ * The DSDT allowed to find out that: ++ * - the driver is required for the ACPI BAT0 device to communicate to the chip ++ * through an operation region. ++ * - the various defines for the operation region functions to communicate with ++ * this driver ++ * - the DSM 3f99e367-6220-4955-8b0f-06ef2ae79412 allows to trigger ACPI ++ * events to BAT0 (the code is all available in the DSDT). ++ * ++ * Further findings regarding the 2 chips declared in the MSHW0011 are: ++ * - there are 2 chips declared: ++ * . 0x22 seems to control the ADP1 line status (and probably the charger) ++ * . 0x55 controls the battery directly ++ * - the battery chip uses a SMBus protocol (using plain SMBus allows non ++ * destructive commands): ++ * . the commands/registers used are in the range 0x00..0x7F ++ * . if bit 8 (0x80) is set in the SMBus command, the returned value is the ++ * same as when it is not set. There is a high chance this bit is the ++ * read/write ++ * . the various registers semantic as been deduced by observing the register ++ * dumps. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define POLL_INTERVAL (2 * HZ) ++ ++struct mshw0011_data { ++ struct i2c_client *adp1; ++ struct i2c_client *bat0; ++ unsigned short notify_mask; ++ struct task_struct *poll_task; ++ bool kthread_running; ++ ++ bool charging; ++ bool bat_charging; ++ u8 trip_point; ++ s32 full_capacity; ++}; ++ ++struct mshw0011_lookup { ++ struct mshw0011_data *cdata; ++ unsigned int n; ++ unsigned int index; ++ int addr; ++}; ++ ++struct mshw0011_handler_data { ++ struct acpi_connection_info info; ++ struct i2c_client *client; ++}; ++ ++struct bix { ++ u32 revision; ++ u32 power_unit; ++ u32 design_capacity; ++ u32 last_full_charg_capacity; ++ u32 battery_technology; ++ u32 design_voltage; ++ u32 design_capacity_of_warning; ++ u32 design_capacity_of_low; ++ u32 cycle_count; ++ u32 measurement_accuracy; ++ u32 max_sampling_time; ++ u32 min_sampling_time; ++ u32 max_average_interval; ++ u32 min_average_interval; ++ u32 battery_capacity_granularity_1; ++ u32 battery_capacity_granularity_2; ++ char model[10]; ++ char serial[10]; ++ char type[10]; ++ char OEM[10]; ++} __packed; ++ ++struct bst { ++ u32 battery_state; ++ s32 battery_present_rate; ++ u32 battery_remaining_capacity; ++ u32 battery_present_voltage; ++} __packed; ++ ++struct gsb_command { ++ u8 arg0; ++ u8 arg1; ++ u8 arg2; ++} __packed; ++ ++struct gsb_buffer { ++ u8 status; ++ u8 len; ++ u8 ret; ++ union { ++ struct gsb_command cmd; ++ struct bst bst; ++ struct bix bix; ++ } __packed; ++} __packed; ++ ++ ++#define ACPI_BATTERY_STATE_DISCHARGING BIT(0) ++#define ACPI_BATTERY_STATE_CHARGING BIT(1) ++#define ACPI_BATTERY_STATE_CRITICAL BIT(2) ++ ++#define MSHW0011_CMD_DEST_BAT0 0x01 ++#define MSHW0011_CMD_DEST_ADP1 0x03 ++ ++#define MSHW0011_CMD_BAT0_STA 0x01 ++#define MSHW0011_CMD_BAT0_BIX 0x02 ++#define MSHW0011_CMD_BAT0_BCT 0x03 ++#define MSHW0011_CMD_BAT0_BTM 0x04 ++#define MSHW0011_CMD_BAT0_BST 0x05 ++#define MSHW0011_CMD_BAT0_BTP 0x06 ++#define MSHW0011_CMD_ADP1_PSR 0x07 ++#define MSHW0011_CMD_BAT0_PSOC 0x09 ++#define MSHW0011_CMD_BAT0_PMAX 0x0a ++#define MSHW0011_CMD_BAT0_PSRC 0x0b ++#define MSHW0011_CMD_BAT0_CHGI 0x0c ++#define MSHW0011_CMD_BAT0_ARTG 0x0d ++ ++#define MSHW0011_NOTIFY_GET_VERSION 0x00 ++#define MSHW0011_NOTIFY_ADP1 0x01 ++#define MSHW0011_NOTIFY_BAT0_BST 0x02 ++#define MSHW0011_NOTIFY_BAT0_BIX 0x05 ++ ++#define MSHW0011_ADP1_REG_PSR 0x04 ++ ++#define MSHW0011_BAT0_REG_CAPACITY 0x0c ++#define MSHW0011_BAT0_REG_FULL_CHG_CAPACITY 0x0e ++#define MSHW0011_BAT0_REG_DESIGN_CAPACITY 0x40 ++#define MSHW0011_BAT0_REG_VOLTAGE 0x08 ++#define MSHW0011_BAT0_REG_RATE 0x14 ++#define MSHW0011_BAT0_REG_OEM 0x45 ++#define MSHW0011_BAT0_REG_TYPE 0x4e ++#define MSHW0011_BAT0_REG_SERIAL_NO 0x56 ++#define MSHW0011_BAT0_REG_CYCLE_CNT 0x6e ++ ++#define MSHW0011_EV_2_5 0x1ff ++ ++static int ++mshw0011_notify(struct mshw0011_data *cdata, u8 arg1, u8 arg2, ++ unsigned int *ret_value) ++{ ++ static const guid_t mshw0011_guid = ++ GUID_INIT(0x3F99E367, 0x6220, 0x4955, ++ 0x8B, 0x0F, 0x06, 0xEF, 0x2A, 0xE7, 0x94, 0x12); ++ union acpi_object *obj; ++ struct acpi_device *adev; ++ acpi_handle handle; ++ unsigned int i; ++ ++ handle = ACPI_HANDLE(&cdata->adp1->dev); ++ if (!handle || acpi_bus_get_device(handle, &adev)) ++ return -ENODEV; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &mshw0011_guid, arg1, arg2, NULL, ++ ACPI_TYPE_BUFFER); ++ if (!obj) { ++ dev_err(&cdata->adp1->dev, "device _DSM execution failed\n"); ++ return -ENODEV; ++ } ++ ++ *ret_value = 0; ++ for (i = 0; i < obj->buffer.length; i++) ++ *ret_value |= obj->buffer.pointer[i] << (i * 8); ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static const struct bix default_bix = { ++ .revision = 0x00, ++ .power_unit = 0x01, ++ .design_capacity = 0x1dca, ++ .last_full_charg_capacity = 0x1dca, ++ .battery_technology = 0x01, ++ .design_voltage = 0x10df, ++ .design_capacity_of_warning = 0x8f, ++ .design_capacity_of_low = 0x47, ++ .cycle_count = 0xffffffff, ++ .measurement_accuracy = 0x00015f90, ++ .max_sampling_time = 0x03e8, ++ .min_sampling_time = 0x03e8, ++ .max_average_interval = 0x03e8, ++ .min_average_interval = 0x03e8, ++ .battery_capacity_granularity_1 = 0x45, ++ .battery_capacity_granularity_2 = 0x11, ++ .model = "P11G8M", ++ .serial = "", ++ .type = "LION", ++ .OEM = "", ++}; ++ ++static int mshw0011_bix(struct mshw0011_data *cdata, struct bix *bix) ++{ ++ struct i2c_client *client = cdata->bat0; ++ char buf[10]; ++ int ret; ++ ++ *bix = default_bix; ++ ++ /* get design capacity */ ++ ret = i2c_smbus_read_word_data(client, ++ MSHW0011_BAT0_REG_DESIGN_CAPACITY); ++ if (ret < 0) { ++ dev_err(&client->dev, "Error reading design capacity: %d\n", ++ ret); ++ return ret; ++ } ++ bix->design_capacity = ret; ++ ++ /* get last full charge capacity */ ++ ret = i2c_smbus_read_word_data(client, ++ MSHW0011_BAT0_REG_FULL_CHG_CAPACITY); ++ if (ret < 0) { ++ dev_err(&client->dev, ++ "Error reading last full charge capacity: %d\n", ret); ++ return ret; ++ } ++ bix->last_full_charg_capacity = ret; ++ ++ /* get serial number */ ++ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_SERIAL_NO, ++ 10, buf); ++ if (ret != 10) { ++ dev_err(&client->dev, "Error reading serial no: %d\n", ret); ++ return ret; ++ } ++ snprintf(bix->serial, ARRAY_SIZE(bix->serial), ++ "%*pE%*pE", 3, buf + 7, 6, buf); ++ ++ /* get cycle count */ ++ ret = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CYCLE_CNT); ++ if (ret < 0) { ++ dev_err(&client->dev, "Error reading cycle count: %d\n", ret); ++ return ret; ++ } ++ bix->cycle_count = ret; ++ ++ /* get OEM name */ ++ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_OEM, ++ 4, buf); ++ if (ret != 4) { ++ dev_err(&client->dev, "Error reading cycle count: %d\n", ret); ++ return ret; ++ } ++ snprintf(bix->OEM, ARRAY_SIZE(bix->OEM), "%*pE", 3, buf); ++ ++ return 0; ++} ++ ++static int mshw0011_bst(struct mshw0011_data *cdata, struct bst *bst) ++{ ++ struct i2c_client *client = cdata->bat0; ++ int rate, capacity, voltage, state; ++ s16 tmp; ++ ++ rate = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_RATE); ++ if (rate < 0) ++ return rate; ++ ++ capacity = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CAPACITY); ++ if (capacity < 0) ++ return capacity; ++ ++ voltage = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_VOLTAGE); ++ if (voltage < 0) ++ return voltage; ++ ++ tmp = rate; ++ bst->battery_present_rate = abs((s32)tmp); ++ ++ state = 0; ++ if ((s32) tmp > 0) ++ state |= ACPI_BATTERY_STATE_CHARGING; ++ else if ((s32) tmp < 0) ++ state |= ACPI_BATTERY_STATE_DISCHARGING; ++ bst->battery_state = state; ++ ++ bst->battery_remaining_capacity = capacity; ++ bst->battery_present_voltage = voltage; ++ ++ return 0; ++} ++ ++static int mshw0011_adp_psr(struct mshw0011_data *cdata) ++{ ++ struct i2c_client *client = cdata->adp1; ++ int ret; ++ ++ ret = i2c_smbus_read_byte_data(client, MSHW0011_ADP1_REG_PSR); ++ if (ret < 0) ++ return ret; ++ ++ return ret; ++} ++ ++static int mshw0011_isr(struct mshw0011_data *cdata) ++{ ++ struct bst bst; ++ struct bix bix; ++ int ret; ++ bool status, bat_status; ++ ++ ret = mshw0011_adp_psr(cdata); ++ if (ret < 0) ++ return ret; ++ ++ status = ret; ++ ++ if (status != cdata->charging) ++ mshw0011_notify(cdata, cdata->notify_mask, ++ MSHW0011_NOTIFY_ADP1, &ret); ++ ++ cdata->charging = status; ++ ++ ret = mshw0011_bst(cdata, &bst); ++ if (ret < 0) ++ return ret; ++ ++ bat_status = bst.battery_state; ++ ++ if (bat_status != cdata->bat_charging) ++ mshw0011_notify(cdata, cdata->notify_mask, ++ MSHW0011_NOTIFY_BAT0_BST, &ret); ++ ++ cdata->bat_charging = bat_status; ++ ++ ret = mshw0011_bix(cdata, &bix); ++ if (ret < 0) ++ return ret; ++ if (bix.last_full_charg_capacity != cdata->full_capacity) ++ mshw0011_notify(cdata, cdata->notify_mask, ++ MSHW0011_NOTIFY_BAT0_BIX, &ret); ++ ++ cdata->full_capacity = bix.last_full_charg_capacity; ++ ++ return 0; ++} ++ ++static int mshw0011_poll_task(void *data) ++{ ++ struct mshw0011_data *cdata = data; ++ int ret = 0; ++ ++ cdata->kthread_running = true; ++ ++ set_freezable(); ++ ++ while (!kthread_should_stop()) { ++ schedule_timeout_interruptible(POLL_INTERVAL); ++ try_to_freeze(); ++ ret = mshw0011_isr(data); ++ if (ret) ++ break; ++ } ++ ++ cdata->kthread_running = false; ++ return ret; ++} ++ ++static acpi_status ++mshw0011_space_handler(u32 function, acpi_physical_address command, ++ u32 bits, u64 *value64, ++ void *handler_context, void *region_context) ++{ ++ struct gsb_buffer *gsb = (struct gsb_buffer *)value64; ++ struct mshw0011_handler_data *data = handler_context; ++ struct acpi_connection_info *info = &data->info; ++ struct acpi_resource_i2c_serialbus *sb; ++ struct i2c_client *client = data->client; ++ struct mshw0011_data *cdata = i2c_get_clientdata(client); ++ struct acpi_resource *ares; ++ u32 accessor_type = function >> 16; ++ acpi_status ret; ++ int status = 1; ++ ++ ret = acpi_buffer_to_resource(info->connection, info->length, &ares); ++ if (ACPI_FAILURE(ret)) ++ return ret; ++ ++ if (!value64 || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } ++ ++ sb = &ares->data.i2c_serial_bus; ++ if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) { ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } ++ ++ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } ++ ++ if (gsb->cmd.arg0 == MSHW0011_CMD_DEST_ADP1 && ++ gsb->cmd.arg1 == MSHW0011_CMD_ADP1_PSR) { ++ ret = mshw0011_adp_psr(cdata); ++ if (ret >= 0) { ++ status = ret; ++ ret = 0; ++ } ++ goto out; ++ } ++ ++ if (gsb->cmd.arg0 != MSHW0011_CMD_DEST_BAT0) { ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } ++ ++ switch (gsb->cmd.arg1) { ++ case MSHW0011_CMD_BAT0_STA: ++ break; ++ case MSHW0011_CMD_BAT0_BIX: ++ ret = mshw0011_bix(cdata, &gsb->bix); ++ break; ++ case MSHW0011_CMD_BAT0_BTP: ++ cdata->trip_point = gsb->cmd.arg2; ++ break; ++ case MSHW0011_CMD_BAT0_BST: ++ ret = mshw0011_bst(cdata, &gsb->bst); ++ break; ++ default: ++ pr_info("command(0x%02x) is not supported.\n", gsb->cmd.arg1); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } ++ ++ out: ++ gsb->ret = status; ++ gsb->status = 0; ++ ++ err: ++ ACPI_FREE(ares); ++ return ret; ++} ++ ++static int mshw0011_install_space_handler(struct i2c_client *client) ++{ ++ acpi_handle handle; ++ struct mshw0011_handler_data *data; ++ acpi_status status; ++ ++ handle = ACPI_HANDLE(&client->dev); ++ ++ if (!handle) ++ return -ENODEV; ++ ++ data = kzalloc(sizeof(struct mshw0011_handler_data), ++ GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->client = client; ++ status = acpi_bus_attach_private_data(handle, (void *)data); ++ if (ACPI_FAILURE(status)) { ++ kfree(data); ++ return -ENOMEM; ++ } ++ ++ status = acpi_install_address_space_handler(handle, ++ ACPI_ADR_SPACE_GSBUS, ++ &mshw0011_space_handler, ++ NULL, ++ data); ++ if (ACPI_FAILURE(status)) { ++ dev_err(&client->dev, "Error installing i2c space handler\n"); ++ acpi_bus_detach_private_data(handle); ++ kfree(data); ++ return -ENOMEM; ++ } ++ ++ acpi_walk_dep_device_list(handle); ++ return 0; ++} ++ ++static void mshw0011_remove_space_handler(struct i2c_client *client) ++{ ++ acpi_handle handle = ACPI_HANDLE(&client->dev); ++ struct mshw0011_handler_data *data; ++ acpi_status status; ++ ++ if (!handle) ++ return; ++ ++ acpi_remove_address_space_handler(handle, ++ ACPI_ADR_SPACE_GSBUS, ++ &mshw0011_space_handler); ++ ++ status = acpi_bus_get_private_data(handle, (void **)&data); ++ if (ACPI_SUCCESS(status)) ++ kfree(data); ++ ++ acpi_bus_detach_private_data(handle); ++} ++ ++static int mshw0011_probe(struct i2c_client *client) ++{ ++ struct i2c_board_info board_info; ++ struct device *dev = &client->dev; ++ struct i2c_client *bat0; ++ ++ struct mshw0011_data *data; ++ int error, mask; ++ ++ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->adp1 = client; ++ i2c_set_clientdata(client, data); ++ ++ memset(&board_info, 0, sizeof(board_info)); ++ strlcpy(board_info.type, "MSHW0011-bat0", I2C_NAME_SIZE); ++ ++ bat0 = i2c_acpi_new_device(dev, 1, &board_info); ++ if (!bat0) ++ return -ENOMEM; ++ ++ data->bat0 = bat0; ++ i2c_set_clientdata(bat0, data); ++ ++ error = mshw0011_notify(data, 1, MSHW0011_NOTIFY_GET_VERSION, &mask); ++ if (error) ++ goto out_err; ++ ++ data->notify_mask = mask == MSHW0011_EV_2_5; ++ ++ data->poll_task = kthread_run(mshw0011_poll_task, data, "mshw0011_adp"); ++ if (IS_ERR(data->poll_task)) { ++ error = PTR_ERR(data->poll_task); ++ dev_err(&client->dev, "Unable to run kthread err %d\n", error); ++ goto out_err; ++ } ++ ++ error = mshw0011_install_space_handler(client); ++ if (error) ++ goto out_err; ++ ++ return 0; ++ ++out_err: ++ if (data->kthread_running) ++ kthread_stop(data->poll_task); ++ i2c_unregister_device(data->bat0); ++ return error; ++} ++ ++static int mshw0011_remove(struct i2c_client *client) ++{ ++ struct mshw0011_data *cdata = i2c_get_clientdata(client); ++ ++ mshw0011_remove_space_handler(client); ++ ++ if (cdata->kthread_running) ++ kthread_stop(cdata->poll_task); ++ ++ i2c_unregister_device(cdata->bat0); ++ ++ return 0; ++} ++ ++static const struct acpi_device_id mshw0011_acpi_match[] = { ++ { "MSHW0011", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, mshw0011_acpi_match); ++ ++static struct i2c_driver mshw0011_driver = { ++ .probe_new = mshw0011_probe, ++ .remove = mshw0011_remove, ++ .driver = { ++ .name = "mshw0011", ++ .acpi_match_table = ACPI_PTR(mshw0011_acpi_match), ++ }, ++}; ++module_i2c_driver(mshw0011_driver); ++ ++MODULE_AUTHOR("Benjamin Tissoires "); ++MODULE_DESCRIPTION("mshw0011 driver"); ++MODULE_LICENSE("GPL v2"); +-- +2.25.0 + diff --git a/patches/5.5/0002-surface3-spi.patch b/patches/5.5/0002-surface3-spi.patch new file mode 100644 index 0000000000..6e57900d55 --- /dev/null +++ b/patches/5.5/0002-surface3-spi.patch @@ -0,0 +1,63 @@ +From fc5a0e4525516353e76a8a3ef348dcd84d935fc4 Mon Sep 17 00:00:00 2001 +From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> +Date: Fri, 6 Dec 2019 23:10:30 +0900 +Subject: [PATCH 2/7] surface3-spi + +--- + drivers/input/touchscreen/surface3_spi.c | 26 ++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c +index ce4828b1415a..63b0b8ddf090 100644 +--- a/drivers/input/touchscreen/surface3_spi.c ++++ b/drivers/input/touchscreen/surface3_spi.c +@@ -25,6 +25,12 @@ + #define SURFACE3_REPORT_TOUCH 0xd2 + #define SURFACE3_REPORT_PEN 0x16 + ++bool use_dma = false; ++module_param(use_dma, bool, 0644); ++MODULE_PARM_DESC(use_dma, ++ "Disable DMA mode if you encounter touch input crash. " ++ "(default: false, disabled to avoid crash)"); ++ + struct surface3_ts_data { + struct spi_device *spi; + struct gpio_desc *gpiod_rst[2]; +@@ -326,6 +332,13 @@ static int surface3_spi_create_pen_input(struct surface3_ts_data *data) + return 0; + } + ++static bool surface3_spi_can_dma(struct spi_controller *ctlr, ++ struct spi_device *spi, ++ struct spi_transfer *tfr) ++{ ++ return use_dma; ++} ++ + static int surface3_spi_probe(struct spi_device *spi) + { + struct surface3_ts_data *data; +@@ -368,6 +381,19 @@ static int surface3_spi_probe(struct spi_device *spi) + if (error) + return error; + ++ /* ++ * Set up DMA ++ * ++ * TODO: Currently, touch input with DMA seems to be broken. ++ * On 4.19 LTS, touch input will crash after suspend. ++ * On recent stable kernel (at least after 5.1), touch input will crash after ++ * the first touch. No problem with PIO on those kernels. ++ * Maybe we need to configure DMA here. ++ * ++ * Link to issue: https://github.com/jakeday/linux-surface/issues/596 ++ */ ++ spi->controller->can_dma = surface3_spi_can_dma; ++ + return 0; + } + +-- +2.25.0 + diff --git a/patches/5.5/0003-surface3-oemb.patch b/patches/5.5/0003-surface3-oemb.patch new file mode 100644 index 0000000000..ec8f8945a5 --- /dev/null +++ b/patches/5.5/0003-surface3-oemb.patch @@ -0,0 +1,69 @@ +From 857c3a83a3a85f1594e05aee5a27a563bda1d6c2 Mon Sep 17 00:00:00 2001 +From: Chih-Wei Huang +Date: Tue, 18 Sep 2018 11:01:37 +0800 +Subject: [PATCH 3/7] surface3-oemb + +--- + drivers/platform/x86/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 6 ++++++ + 3 files changed, 22 insertions(+) + +diff --git a/drivers/platform/x86/surface3-wmi.c b/drivers/platform/x86/surface3-wmi.c +index 130b6f52a600..801083aa56d6 100644 +--- a/drivers/platform/x86/surface3-wmi.c ++++ b/drivers/platform/x86/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 92d67010aeed..cdfd75acd1a5 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3681,6 +3681,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index d0fb43c2b9f6..de2583918afd 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -26,6 +26,12 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, + }, + { } + }; +-- +2.25.0 + diff --git a/patches/5.5/0004-surface-sam.patch b/patches/5.5/0004-surface-sam.patch new file mode 100644 index 0000000000..f47eb32870 --- /dev/null +++ b/patches/5.5/0004-surface-sam.patch @@ -0,0 +1,7435 @@ +From c1b2e8d597323255dc65c1d5e1562b795ba3492c Mon Sep 17 00:00:00 2001 +From: qzed +Date: Mon, 26 Aug 2019 01:11:08 +0200 +Subject: [PATCH 4/7] surface-sam + +--- + drivers/acpi/acpica/dsopcode.c | 2 +- + drivers/acpi/acpica/exfield.c | 12 +- + drivers/platform/x86/Kconfig | 1 + + drivers/platform/x86/Makefile | 1 + + drivers/platform/x86/surface_sam/Kconfig | 163 ++ + drivers/platform/x86/surface_sam/Makefile | 10 + + .../x86/surface_sam/surface_sam_dtx.c | 623 ++++++ + .../x86/surface_sam/surface_sam_hps.c | 1110 +++++++++++ + .../x86/surface_sam/surface_sam_san.c | 901 +++++++++ + .../x86/surface_sam/surface_sam_san.h | 29 + + .../x86/surface_sam/surface_sam_sid.c | 117 ++ + .../x86/surface_sam/surface_sam_sid_gpelid.c | 219 ++ + .../surface_sam/surface_sam_sid_perfmode.c | 225 +++ + .../x86/surface_sam/surface_sam_sid_power.c | 1259 ++++++++++++ + .../x86/surface_sam/surface_sam_sid_vhf.c | 440 ++++ + .../x86/surface_sam/surface_sam_ssh.c | 1773 +++++++++++++++++ + .../x86/surface_sam/surface_sam_ssh.h | 97 + + .../x86/surface_sam/surface_sam_vhf.c | 276 +++ + 18 files changed, 7254 insertions(+), 4 deletions(-) + create mode 100644 drivers/platform/x86/surface_sam/Kconfig + create mode 100644 drivers/platform/x86/surface_sam/Makefile + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_dtx.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_hps.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_san.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_san.h + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_power.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.c + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.h + create mode 100644 drivers/platform/x86/surface_sam/surface_sam_vhf.c + +diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c +index 10f32b62608e..7b2a4987f050 100644 +--- a/drivers/acpi/acpica/dsopcode.c ++++ b/drivers/acpi/acpica/dsopcode.c +@@ -123,7 +123,7 @@ acpi_ds_init_buffer_field(u16 aml_opcode, + + /* Offset is in bits, count is in bits */ + +- field_flags = AML_FIELD_ACCESS_BYTE; ++ field_flags = AML_FIELD_ACCESS_BUFFER; + bit_offset = offset; + bit_count = (u32) length_desc->integer.value; + +diff --git a/drivers/acpi/acpica/exfield.c b/drivers/acpi/acpica/exfield.c +index d3d2dbfba680..0b7f617a6e9b 100644 +--- a/drivers/acpi/acpica/exfield.c ++++ b/drivers/acpi/acpica/exfield.c +@@ -109,6 +109,7 @@ acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, + union acpi_operand_object *buffer_desc; + void *buffer; + u32 buffer_length; ++ u8 field_flags; + + ACPI_FUNCTION_TRACE_PTR(ex_read_data_from_field, obj_desc); + +@@ -157,11 +158,16 @@ acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, + * Note: Field.length is in bits. + */ + buffer_length = +- (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->field.bit_length); ++ (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->common_field.bit_length); ++ field_flags = obj_desc->common_field.field_flags; + +- if (buffer_length > acpi_gbl_integer_byte_width) { ++ if (buffer_length > acpi_gbl_integer_byte_width || ++ (field_flags & AML_FIELD_ACCESS_TYPE_MASK) == AML_FIELD_ACCESS_BUFFER) { + +- /* Field is too large for an Integer, create a Buffer instead */ ++ /* ++ * Field is either too large for an Integer, or a actually of type ++ * buffer, so create a Buffer. ++ */ + + buffer_desc = acpi_ut_create_buffer_object(buffer_length); + if (!buffer_desc) { +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 0e389c2e2101..4673aed07e98 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -1345,6 +1345,7 @@ config PCENGINES_APU2 + will be called pcengines-apuv2. + + source "drivers/platform/x86/intel_speed_select_if/Kconfig" ++source "drivers/platform/x86/surface_sam/Kconfig" + + config SYSTEM76_ACPI + tristate "System76 ACPI Driver" +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index d707a8edd738..56ebd493b38f 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -106,3 +106,4 @@ obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o + obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o + obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ + obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o ++obj-$(CONFIG_SURFACE_SAM) += surface_sam/ +diff --git a/drivers/platform/x86/surface_sam/Kconfig b/drivers/platform/x86/surface_sam/Kconfig +new file mode 100644 +index 000000000000..b4513c234c4d +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/Kconfig +@@ -0,0 +1,163 @@ ++menuconfig SURFACE_SAM ++ depends on ACPI ++ tristate "Microsoft Surface/System Aggregator Module and Platform Drivers" ++ ---help--- ++ Drivers for the Surface/System Aggregator Module (SAM) of Microsoft ++ Surface devices. ++ ++ SAM is an embedded controller that provides access to various ++ functionalities on these devices, including battery status, keyboard ++ events (on the Laptops) and many more. ++ ++ Say M/Y here if you have a Microsoft Surface device with a SAM device ++ (i.e. 5th generation or later). ++ ++config SURFACE_SAM_SSH ++ tristate "Surface Serial Hub Driver" ++ depends on SURFACE_SAM ++ depends on SERIAL_DEV_CTRL_TTYPORT ++ select CRC_CCITT ++ default m ++ ---help--- ++ Surface Serial Hub driver for 5th generation (or later) Microsoft ++ Surface devices. ++ ++ This is the base driver for the embedded serial controller found on ++ 5th generation (and later) Microsoft Surface devices (e.g. Book 2, ++ Laptop, Laptop 2, Pro 2017, Pro 6, ...). This driver itself only ++ provides access to the embedded controller (SAM) and subsequent ++ drivers are required for the respective functionalities. ++ ++ If you have a 5th generation (or later) Microsoft Surface device, say ++ Y or M here. ++ ++config SURFACE_SAM_SSH_DEBUG_DEVICE ++ bool "Surface Serial Hub Debug Device" ++ depends on SURFACE_SAM_SSH ++ depends on SYSFS ++ default n ++ ---help--- ++ Debug device for direct communication with the embedded controller ++ found on 5th generation (and later) Microsoft Surface devices (e.g. ++ Book 2, Laptop, Laptop 2, Pro 2017, Pro 6, ...) via sysfs. ++ ++ If you are not sure, say N here. ++ ++config SURFACE_SAM_SAN ++ tristate "Surface ACPI Notify Driver" ++ depends on SURFACE_SAM_SSH ++ default m ++ ---help--- ++ Surface ACPI Notify driver for 5th generation (or later) Microsoft ++ Surface devices. ++ ++ This driver enables basic ACPI events and requests, such as battery ++ status requests/events, thermal events, lid status, and possibly more, ++ which would otherwise not work on these devices. ++ ++ If you are not sure, say M here. ++ ++config SURFACE_SAM_VHF ++ tristate "Surface Virtual HID Framework Driver" ++ depends on SURFACE_SAM_SSH ++ depends on HID ++ default m ++ ---help--- ++ Surface Virtual HID Framework driver for 5th generation (or later) ++ Microsoft Surface devices. ++ ++ This driver provides support for the Microsoft Virtual HID framework, ++ which is required for keyboard support on the Surface Laptop 1 and 2. ++ ++ If you are not sure, say M here. ++ ++config SURFACE_SAM_DTX ++ tristate "Surface Detachment System (DTX) Driver" ++ depends on SURFACE_SAM_SSH ++ depends on INPUT ++ default m ++ ---help--- ++ Surface Detachment System (DTX) driver for the Microsoft Surface Book ++ 2. This driver provides support for proper detachment handling in ++ user-space, status-events relating to the base and support for ++ the safe-guard keeping the base attached when the discrete GPU ++ contained in it is running via the special /dev/surface-dtx device. ++ ++ Also provides a standard input device to provide SW_TABLET_MODE events ++ upon device mode change. ++ ++ If you are not sure, say M here. ++ ++config SURFACE_SAM_HPS ++ tristate "Surface dGPU Hot-Plug System (dGPU-HPS) Driver" ++ depends on SURFACE_SAM_SSH ++ depends on SURFACE_SAM_SAN ++ default m ++ ---help--- ++ Driver to properly handle hot-plugging and explicit power-on/power-off ++ of the discrete GPU (dGPU) on the Surface Book 2. ++ ++ If you are not sure, say M here. ++ ++config SURFACE_SAM_SID ++ tristate "Surface Platform Integration Driver" ++ depends on SURFACE_SAM_SSH ++ default m ++ ---help--- ++ Surface Platform Integration Driver for the Microsoft Surface Devices. ++ This driver loads various model-specific sub-drivers, including ++ battery and keyboard support on 7th generation Surface devices, proper ++ lid setup to enable device wakeup when the lid is opened on multiple ++ models, as well as performance mode setting support on the Surface ++ Book 2. ++ ++ If you are not sure, say M here. ++ ++config SURFACE_SAM_SID_GPELID ++ tristate "Surface Lid Wakeup Driver" ++ depends on SURFACE_SAM_SID ++ default m ++ ---help--- ++ Driver to set up device wake-up via lid on Intel-based Microsoft ++ Surface devices. These devices do not wake up from sleep as their GPE ++ interrupt is not configured automatically. This driver solves that ++ problem. ++ ++ If you are not sure, say M here. ++ ++config SURFACE_SAM_SID_PERFMODE ++ tristate "Surface Performance Mode Driver" ++ depends on SURFACE_SAM_SID ++ depends on SYSFS ++ default m ++ ---help--- ++ This driver provides suport for setting performance-modes on Surface ++ devices via the perf_mode sysfs attribute. Currently only supports the ++ Surface Book 2. Performance-modes directly influence the fan-profile ++ of the device, allowing to choose between higher performance or ++ quieter operation. ++ ++ If you are not sure, say M here. ++ ++config SURFACE_SAM_SID_VHF ++ tristate "Surface SAM HID Driver" ++ depends on SURFACE_SAM_SID ++ depends on HID ++ default m ++ ---help--- ++ This driver provides support for HID devices connected via the Surface ++ SAM embedded controller. It provides support for keyboard and touchpad ++ on the Surface Laptop 3 models. ++ ++ If you are not sure, say M here. ++ ++config SURFACE_SAM_SID_POWER ++ tristate "Surface SAM Battery/AC Driver" ++ depends on SURFACE_SAM_SID ++ select POWER_SUPPLY ++ default m ++ ---help--- ++ This driver provides support for the battery and AC on 7th generation ++ Surface devices. ++ ++ If you are not sure, say M here. +diff --git a/drivers/platform/x86/surface_sam/Makefile b/drivers/platform/x86/surface_sam/Makefile +new file mode 100644 +index 000000000000..188975ccde5c +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/Makefile +@@ -0,0 +1,10 @@ ++obj-$(CONFIG_SURFACE_SAM_SSH) += surface_sam_ssh.o ++obj-$(CONFIG_SURFACE_SAM_SAN) += surface_sam_san.o ++obj-$(CONFIG_SURFACE_SAM_DTX) += surface_sam_dtx.o ++obj-$(CONFIG_SURFACE_SAM_HPS) += surface_sam_hps.o ++obj-$(CONFIG_SURFACE_SAM_VHF) += surface_sam_vhf.o ++obj-$(CONFIG_SURFACE_SAM_SID) += surface_sam_sid.o ++obj-$(CONFIG_SURFACE_SAM_SID_GPELID) += surface_sam_sid_gpelid.o ++obj-$(CONFIG_SURFACE_SAM_SID_PERFMODE) += surface_sam_sid_perfmode.o ++obj-$(CONFIG_SURFACE_SAM_SID_POWER) += surface_sam_sid_power.o ++obj-$(CONFIG_SURFACE_SAM_SID_VHF) += surface_sam_sid_vhf.o +diff --git a/drivers/platform/x86/surface_sam/surface_sam_dtx.c b/drivers/platform/x86/surface_sam/surface_sam_dtx.c +new file mode 100644 +index 000000000000..4b924de6ab09 +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_dtx.c +@@ -0,0 +1,623 @@ ++/* ++ * Detachment system (DTX) driver for Microsoft Surface Book 2. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "surface_sam_ssh.h" ++ ++ ++#define USB_VENDOR_ID_MICROSOFT 0x045e ++#define USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION 0x0922 ++ ++// name copied from MS device manager ++#define DTX_INPUT_NAME "Microsoft Surface Base 2 Integration Device" ++ ++ ++#define DTX_CMD_LATCH_LOCK _IO(0x11, 0x01) ++#define DTX_CMD_LATCH_UNLOCK _IO(0x11, 0x02) ++#define DTX_CMD_LATCH_REQUEST _IO(0x11, 0x03) ++#define DTX_CMD_LATCH_OPEN _IO(0x11, 0x04) ++#define DTX_CMD_GET_OPMODE _IOR(0x11, 0x05, int) ++ ++#define SAM_RQST_DTX_TC 0x11 ++#define SAM_RQST_DTX_CID_LATCH_LOCK 0x06 ++#define SAM_RQST_DTX_CID_LATCH_UNLOCK 0x07 ++#define SAM_RQST_DTX_CID_LATCH_REQUEST 0x08 ++#define SAM_RQST_DTX_CID_LATCH_OPEN 0x09 ++#define SAM_RQST_DTX_CID_GET_OPMODE 0x0D ++ ++#define SAM_EVENT_DTX_TC 0x11 ++#define SAM_EVENT_DTX_RQID 0x0011 ++#define SAM_EVENT_DTX_CID_CONNECTION 0x0c ++#define SAM_EVENT_DTX_CID_BUTTON 0x0e ++#define SAM_EVENT_DTX_CID_ERROR 0x0f ++#define SAM_EVENT_DTX_CID_LATCH_STATUS 0x11 ++ ++#define DTX_OPMODE_TABLET 0x00 ++#define DTX_OPMODE_LAPTOP 0x01 ++#define DTX_OPMODE_STUDIO 0x02 ++ ++#define DTX_LATCH_CLOSED 0x00 ++#define DTX_LATCH_OPENED 0x01 ++ ++ ++// Warning: This must always be a power of 2! ++#define DTX_CLIENT_BUF_SIZE 16 ++ ++#define DTX_CONNECT_OPMODE_DELAY 1000 ++ ++#define DTX_ERR KERN_ERR "surface_sam_dtx: " ++#define DTX_WARN KERN_WARNING "surface_sam_dtx: " ++ ++ ++struct surface_dtx_event { ++ u8 type; ++ u8 code; ++ u8 arg0; ++ u8 arg1; ++} __packed; ++ ++struct surface_dtx_dev { ++ wait_queue_head_t waitq; ++ struct miscdevice mdev; ++ spinlock_t client_lock; ++ struct list_head client_list; ++ struct mutex mutex; ++ bool active; ++ spinlock_t input_lock; ++ struct input_dev *input_dev; ++}; ++ ++struct surface_dtx_client { ++ struct list_head node; ++ struct surface_dtx_dev *ddev; ++ struct fasync_struct *fasync; ++ spinlock_t buffer_lock; ++ unsigned int buffer_head; ++ unsigned int buffer_tail; ++ struct surface_dtx_event buffer[DTX_CLIENT_BUF_SIZE]; ++}; ++ ++ ++static struct surface_dtx_dev surface_dtx_dev; ++ ++ ++static int surface_sam_query_opmpde(void) ++{ ++ u8 result_buf[1]; ++ int status; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = SAM_RQST_DTX_TC, ++ .cid = SAM_RQST_DTX_CID_GET_OPMODE, ++ .iid = 0, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 1, ++ .cdl = 0, ++ .pld = NULL, ++ }; ++ ++ struct surface_sam_ssh_buf result = { ++ .cap = 1, ++ .len = 0, ++ .data = result_buf, ++ }; ++ ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ if (status) { ++ return status; ++ } ++ ++ if (result.len != 1) { ++ return -EFAULT; ++ } ++ ++ return result.data[0]; ++} ++ ++ ++static int dtx_cmd_simple(u8 cid) ++{ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = SAM_RQST_DTX_TC, ++ .cid = cid, ++ .iid = 0, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 0, ++ .cdl = 0, ++ .pld = NULL, ++ }; ++ ++ return surface_sam_ssh_rqst(&rqst, NULL); ++} ++ ++static int dtx_cmd_get_opmode(int __user *buf) ++{ ++ int opmode = surface_sam_query_opmpde(); ++ if (opmode < 0) { ++ return opmode; ++ } ++ ++ if (put_user(opmode, buf)) { ++ return -EACCES; ++ } ++ ++ return 0; ++} ++ ++ ++static int surface_dtx_open(struct inode *inode, struct file *file) ++{ ++ struct surface_dtx_dev *ddev = container_of(file->private_data, struct surface_dtx_dev, mdev); ++ struct surface_dtx_client *client; ++ ++ // initialize client ++ client = kzalloc(sizeof(struct surface_dtx_client), GFP_KERNEL); ++ if (!client) { ++ return -ENOMEM; ++ } ++ ++ spin_lock_init(&client->buffer_lock); ++ client->buffer_head = 0; ++ client->buffer_tail = 0; ++ client->ddev = ddev; ++ ++ // attach client ++ spin_lock(&ddev->client_lock); ++ list_add_tail_rcu(&client->node, &ddev->client_list); ++ spin_unlock(&ddev->client_lock); ++ ++ file->private_data = client; ++ nonseekable_open(inode, file); ++ ++ return 0; ++} ++ ++static int surface_dtx_release(struct inode *inode, struct file *file) ++{ ++ struct surface_dtx_client *client = file->private_data; ++ ++ // detach client ++ spin_lock(&client->ddev->client_lock); ++ list_del_rcu(&client->node); ++ spin_unlock(&client->ddev->client_lock); ++ synchronize_rcu(); ++ ++ kfree(client); ++ file->private_data = NULL; ++ ++ return 0; ++} ++ ++static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) ++{ ++ struct surface_dtx_client *client = file->private_data; ++ struct surface_dtx_dev *ddev = client->ddev; ++ struct surface_dtx_event event; ++ size_t read = 0; ++ int status = 0; ++ ++ if (count != 0 && count < sizeof(struct surface_dtx_event)) { ++ return -EINVAL; ++ } ++ ++ if (!ddev->active) { ++ return -ENODEV; ++ } ++ ++ // check availability ++ if (client->buffer_head == client->buffer_tail){ ++ if (file->f_flags & O_NONBLOCK) { ++ return -EAGAIN; ++ } ++ ++ status = wait_event_interruptible(ddev->waitq, ++ client->buffer_head != client->buffer_tail || ++ !ddev->active); ++ if (status) { ++ return status; ++ } ++ ++ if (!ddev->active) { ++ return -ENODEV; ++ } ++ } ++ ++ // copy events one by one ++ while (read + sizeof(struct surface_dtx_event) <= count) { ++ spin_lock_irq(&client->buffer_lock); ++ ++ if(client->buffer_head == client->buffer_tail) { ++ spin_unlock_irq(&client->buffer_lock); ++ break; ++ } ++ ++ // get one event ++ event = client->buffer[client->buffer_tail]; ++ client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1); ++ spin_unlock_irq(&client->buffer_lock); ++ ++ // copy to userspace ++ if(copy_to_user(buf, &event, sizeof(struct surface_dtx_event))) { ++ return -EFAULT; ++ } ++ ++ read += sizeof(struct surface_dtx_event); ++ } ++ ++ return read; ++} ++ ++static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) ++{ ++ struct surface_dtx_client *client = file->private_data; ++ int mask; ++ ++ poll_wait(file, &client->ddev->waitq, pt); ++ ++ if (client->ddev->active) { ++ mask = EPOLLOUT | EPOLLWRNORM; ++ } else { ++ mask = EPOLLHUP | EPOLLERR; ++ } ++ ++ if (client->buffer_head != client->buffer_tail) { ++ mask |= EPOLLIN | EPOLLRDNORM; ++ } ++ ++ return mask; ++} ++ ++static int surface_dtx_fasync(int fd, struct file *file, int on) ++{ ++ struct surface_dtx_client *client = file->private_data; ++ ++ return fasync_helper(fd, file, on, &client->fasync); ++} ++ ++static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct surface_dtx_client *client = file->private_data; ++ struct surface_dtx_dev *ddev = client->ddev; ++ int status; ++ ++ status = mutex_lock_interruptible(&ddev->mutex); ++ if (status) { ++ return status; ++ } ++ ++ if (!ddev->active) { ++ mutex_unlock(&ddev->mutex); ++ return -ENODEV; ++ } ++ ++ switch (cmd) { ++ case DTX_CMD_LATCH_LOCK: ++ status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_LOCK); ++ break; ++ ++ case DTX_CMD_LATCH_UNLOCK: ++ status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_UNLOCK); ++ break; ++ ++ case DTX_CMD_LATCH_REQUEST: ++ status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_REQUEST); ++ break; ++ ++ case DTX_CMD_LATCH_OPEN: ++ status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_OPEN); ++ break; ++ ++ case DTX_CMD_GET_OPMODE: ++ status = dtx_cmd_get_opmode((int __user *)arg); ++ break; ++ ++ default: ++ status = -EINVAL; ++ break; ++ } ++ ++ mutex_unlock(&ddev->mutex); ++ return status; ++} ++ ++static const struct file_operations surface_dtx_fops = { ++ .owner = THIS_MODULE, ++ .open = surface_dtx_open, ++ .release = surface_dtx_release, ++ .read = surface_dtx_read, ++ .poll = surface_dtx_poll, ++ .fasync = surface_dtx_fasync, ++ .unlocked_ioctl = surface_dtx_ioctl, ++ .llseek = no_llseek, ++}; ++ ++static struct surface_dtx_dev surface_dtx_dev = { ++ .mdev = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = "surface_dtx", ++ .fops = &surface_dtx_fops, ++ }, ++ .client_lock = __SPIN_LOCK_UNLOCKED(), ++ .input_lock = __SPIN_LOCK_UNLOCKED(), ++ .mutex = __MUTEX_INITIALIZER(surface_dtx_dev.mutex), ++ .active = false, ++}; ++ ++ ++static void surface_dtx_push_event(struct surface_dtx_dev *ddev, struct surface_dtx_event *event) ++{ ++ struct surface_dtx_client *client; ++ ++ rcu_read_lock(); ++ list_for_each_entry_rcu(client, &ddev->client_list, node) { ++ spin_lock(&client->buffer_lock); ++ ++ client->buffer[client->buffer_head++] = *event; ++ client->buffer_head &= DTX_CLIENT_BUF_SIZE - 1; ++ ++ if (unlikely(client->buffer_head == client->buffer_tail)) { ++ printk(DTX_WARN "event buffer overrun\n"); ++ client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1); ++ } ++ ++ spin_unlock(&client->buffer_lock); ++ ++ kill_fasync(&client->fasync, SIGIO, POLL_IN); ++ } ++ rcu_read_unlock(); ++ ++ wake_up_interruptible(&ddev->waitq); ++} ++ ++ ++static void surface_dtx_update_opmpde(struct surface_dtx_dev *ddev) ++{ ++ struct surface_dtx_event event; ++ int opmode; ++ ++ // get operation mode ++ opmode = surface_sam_query_opmpde(); ++ if (opmode < 0) { ++ printk(DTX_ERR "EC request failed with error %d\n", opmode); ++ } ++ ++ // send DTX event ++ event.type = 0x11; ++ event.code = 0x0D; ++ event.arg0 = opmode; ++ event.arg1 = 0x00; ++ ++ surface_dtx_push_event(ddev, &event); ++ ++ // send SW_TABLET_MODE event ++ spin_lock(&ddev->input_lock); ++ input_report_switch(ddev->input_dev, SW_TABLET_MODE, opmode == 0x00); ++ input_sync(ddev->input_dev); ++ spin_unlock(&ddev->input_lock); ++} ++ ++static int surface_dtx_evt_dtx(struct surface_sam_ssh_event *in_event, void *data) ++{ ++ struct surface_dtx_dev *ddev = data; ++ struct surface_dtx_event event; ++ ++ switch (in_event->cid) { ++ case SAM_EVENT_DTX_CID_CONNECTION: ++ case SAM_EVENT_DTX_CID_BUTTON: ++ case SAM_EVENT_DTX_CID_ERROR: ++ case SAM_EVENT_DTX_CID_LATCH_STATUS: ++ if (in_event->len > 2) { ++ printk(DTX_ERR "unexpected payload size (cid: %x, len: %u)\n", ++ in_event->cid, in_event->len); ++ return 0; ++ } ++ ++ event.type = in_event->tc; ++ event.code = in_event->cid; ++ event.arg0 = in_event->len >= 1 ? in_event->pld[0] : 0x00; ++ event.arg1 = in_event->len >= 2 ? in_event->pld[1] : 0x00; ++ surface_dtx_push_event(ddev, &event); ++ break; ++ ++ default: ++ printk(DTX_WARN "unhandled dtx event (cid: %x)\n", in_event->cid); ++ } ++ ++ // update device mode ++ if (in_event->cid == SAM_EVENT_DTX_CID_CONNECTION) { ++ if (in_event->pld[0]) { ++ // Note: we're already in a workqueue task ++ msleep(DTX_CONNECT_OPMODE_DELAY); ++ } ++ ++ surface_dtx_update_opmpde(ddev); ++ } ++ ++ return 0; ++} ++ ++static int surface_dtx_events_setup(struct surface_dtx_dev *ddev) ++{ ++ int status; ++ ++ status = surface_sam_ssh_set_event_handler(SAM_EVENT_DTX_RQID, surface_dtx_evt_dtx, ddev); ++ if (status) { ++ goto err_handler; ++ } ++ ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID); ++ if (status) { ++ goto err_source; ++ } ++ ++ return 0; ++ ++err_source: ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID); ++err_handler: ++ return status; ++} ++ ++static void surface_dtx_events_disable(void) ++{ ++ surface_sam_ssh_disable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID); ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID); ++} ++ ++ ++static struct input_dev *surface_dtx_register_inputdev(struct platform_device *pdev) ++{ ++ struct input_dev *input_dev; ++ int status; ++ ++ input_dev = input_allocate_device(); ++ if (!input_dev) { ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ input_dev->name = DTX_INPUT_NAME; ++ input_dev->dev.parent = &pdev->dev; ++ input_dev->id.bustype = BUS_VIRTUAL; ++ input_dev->id.vendor = USB_VENDOR_ID_MICROSOFT; ++ input_dev->id.product = USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION; ++ ++ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); ++ ++ status = surface_sam_query_opmpde(); ++ if (status < 0) { ++ input_free_device(input_dev); ++ return ERR_PTR(status); ++ } ++ ++ input_report_switch(input_dev, SW_TABLET_MODE, status == 0x00); ++ ++ status = input_register_device(input_dev); ++ if (status) { ++ input_unregister_device(input_dev); ++ return ERR_PTR(status); ++ } ++ ++ return input_dev; ++} ++ ++ ++static int surface_sam_dtx_probe(struct platform_device *pdev) ++{ ++ struct surface_dtx_dev *ddev = &surface_dtx_dev; ++ struct input_dev *input_dev; ++ int status; ++ ++ // link to ec ++ status = surface_sam_ssh_consumer_register(&pdev->dev); ++ if (status) { ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ } ++ ++ input_dev = surface_dtx_register_inputdev(pdev); ++ if (IS_ERR(input_dev)) { ++ return PTR_ERR(input_dev); ++ } ++ ++ // initialize device ++ mutex_lock(&ddev->mutex); ++ if (ddev->active) { ++ mutex_unlock(&ddev->mutex); ++ status = -ENODEV; ++ goto err_register; ++ } ++ ++ INIT_LIST_HEAD(&ddev->client_list); ++ init_waitqueue_head(&ddev->waitq); ++ ddev->active = true; ++ ddev->input_dev = input_dev; ++ mutex_unlock(&ddev->mutex); ++ ++ status = misc_register(&ddev->mdev); ++ if (status) { ++ goto err_register; ++ } ++ ++ // enable events ++ status = surface_dtx_events_setup(ddev); ++ if (status) { ++ goto err_events_setup; ++ } ++ ++ return 0; ++ ++err_events_setup: ++ misc_deregister(&ddev->mdev); ++err_register: ++ input_unregister_device(ddev->input_dev); ++ return status; ++} ++ ++static int surface_sam_dtx_remove(struct platform_device *pdev) ++{ ++ struct surface_dtx_dev *ddev = &surface_dtx_dev; ++ struct surface_dtx_client *client; ++ ++ mutex_lock(&ddev->mutex); ++ if (!ddev->active) { ++ mutex_unlock(&ddev->mutex); ++ return 0; ++ } ++ ++ // mark as inactive ++ ddev->active = false; ++ mutex_unlock(&ddev->mutex); ++ ++ // After this call we're guaranteed that no more input events will arive ++ surface_dtx_events_disable(); ++ ++ // wake up clients ++ spin_lock(&ddev->client_lock); ++ list_for_each_entry(client, &ddev->client_list, node) { ++ kill_fasync(&client->fasync, SIGIO, POLL_HUP); ++ } ++ spin_unlock(&ddev->client_lock); ++ ++ wake_up_interruptible(&ddev->waitq); ++ ++ // unregister user-space devices ++ input_unregister_device(ddev->input_dev); ++ misc_deregister(&ddev->mdev); ++ ++ return 0; ++} ++ ++ ++static const struct acpi_device_id surface_sam_dtx_match[] = { ++ { "MSHW0133", 0 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_sam_dtx_match); ++ ++static struct platform_driver surface_sam_dtx = { ++ .probe = surface_sam_dtx_probe, ++ .remove = surface_sam_dtx_remove, ++ .driver = { ++ .name = "surface_sam_dtx", ++ .acpi_match_table = ACPI_PTR(surface_sam_dtx_match), ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_sam_dtx); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface Detachment System (DTX) Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_hps.c b/drivers/platform/x86/surface_sam/surface_sam_hps.c +new file mode 100644 +index 000000000000..3b123bd3dcfe +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_hps.c +@@ -0,0 +1,1110 @@ ++/* ++ * Surface dGPU hot-plug system driver. ++ * Supports explicit setting of the dGPU power-state on the Surface Book 2 and ++ * properly handles hot-plugging by detaching the base. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "surface_sam_ssh.h" ++#include "surface_sam_san.h" ++ ++ ++// TODO: vgaswitcheroo integration ++ ++ ++static void dbg_dump_drvsta(struct platform_device *pdev, const char *prefix); ++ ++ ++#define SHPS_DSM_REVISION 1 ++#define SHPS_DSM_GPU_ADDRS 0x02 ++#define SHPS_DSM_GPU_POWER 0x05 ++static const guid_t SHPS_DSM_UUID = ++ GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, ++ 0x32, 0x0e, 0x10, 0x36, 0x0a); ++ ++ ++#define SAM_DGPU_TC 0x13 ++#define SAM_DGPU_CID_POWERON 0x02 ++ ++#define SAM_DTX_TC 0x11 ++#define SAM_DTX_CID_LATCH_LOCK 0x06 ++#define SAM_DTX_CID_LATCH_UNLOCK 0x07 ++ ++#define SHPS_DSM_GPU_ADDRS_RP "RP5_PCIE" ++#define SHPS_DSM_GPU_ADDRS_DGPU "DGPU_PCIE" ++ ++ ++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 shps_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 shps_dgpu_power { ++ SHPS_DGPU_POWER_OFF = 0, ++ SHPS_DGPU_POWER_ON = 1, ++ SHPS_DGPU_POWER_UNKNOWN = 2, ++}; ++ ++static const char* shps_dgpu_power_str(enum shps_dgpu_power power) { ++ if (power == SHPS_DGPU_POWER_OFF) ++ return "off"; ++ else if (power == SHPS_DGPU_POWER_ON) ++ return "on"; ++ else if (power == SHPS_DGPU_POWER_UNKNOWN) ++ return "unknown"; ++ else ++ return ""; ++} ++ ++ ++struct shps_driver_data { ++ struct mutex lock; ++ struct pci_dev *dgpu_root_port; ++ struct pci_saved_state *dgpu_root_port_state; ++ struct gpio_desc *gpio_dgpu_power; ++ struct gpio_desc *gpio_dgpu_presence; ++ struct gpio_desc *gpio_base_presence; ++ unsigned int irq_dgpu_presence; ++ unsigned int irq_base_presence; ++ unsigned long state; ++}; ++ ++#define SHPS_STATE_BIT_PWRTGT 0 /* desired power state: 1 for on, 0 for off */ ++#define SHPS_STATE_BIT_RPPWRON_SYNC 1 /* synchronous/requested power-up in progress */ ++#define SHPS_STATE_BIT_WAKE_ENABLED 2 /* wakeup via base-presence GPIO enabled */ ++ ++ ++#define SHPS_DGPU_PARAM_PERM (S_IRUGO | S_IWUSR) ++ ++enum shps_dgpu_power_mp { ++ SHPS_DGPU_MP_POWER_OFF = SHPS_DGPU_POWER_OFF, ++ SHPS_DGPU_MP_POWER_ON = SHPS_DGPU_POWER_ON, ++ SHPS_DGPU_MP_POWER_ASIS = -1, ++ ++ __SHPS_DGPU_MP_POWER_START = -1, ++ __SHPS_DGPU_MP_POWER_END = 1, ++}; ++ ++static int param_dgpu_power_set(const char *val, const struct kernel_param *kp) ++{ ++ int power = SHPS_DGPU_MP_POWER_OFF; ++ int status; ++ ++ status = kstrtoint(val, 0, &power); ++ if (status) { ++ return status; ++ } ++ ++ if (power < __SHPS_DGPU_MP_POWER_START || power > __SHPS_DGPU_MP_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 = SHPS_DGPU_MP_POWER_OFF; ++static int param_dgpu_power_exit = SHPS_DGPU_MP_POWER_ON; ++static int param_dgpu_power_susp = SHPS_DGPU_MP_POWER_ASIS; ++static bool param_dtx_latch = true; ++ ++module_param_cb(dgpu_power_init, ¶m_dgpu_power_ops, ¶m_dgpu_power_init, SHPS_DGPU_PARAM_PERM); ++module_param_cb(dgpu_power_exit, ¶m_dgpu_power_ops, ¶m_dgpu_power_exit, SHPS_DGPU_PARAM_PERM); ++module_param_cb(dgpu_power_susp, ¶m_dgpu_power_ops, ¶m_dgpu_power_susp, SHPS_DGPU_PARAM_PERM); ++module_param_named(dtx_latch, param_dtx_latch, bool, SHPS_DGPU_PARAM_PERM); ++ ++MODULE_PARM_DESC(dgpu_power_init, "dGPU power state to be set on init (0: off / 1: on / 2: as-is, default: off)"); ++MODULE_PARM_DESC(dgpu_power_exit, "dGPU power state to be set on exit (0: off / 1: on / 2: as-is, default: on)"); ++MODULE_PARM_DESC(dgpu_power_susp, "dGPU power state to be set on exit (0: off / 1: on / 2: as-is, default: as-is)"); ++MODULE_PARM_DESC(dtx_latch, "lock/unlock DTX base latch in accordance to power-state (Y/n)"); ++ ++ ++static int dtx_cmd_simple(u8 cid) ++{ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = SAM_DTX_TC, ++ .cid = cid, ++ .iid = 0, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 0, ++ .cdl = 0, ++ .pld = NULL, ++ }; ++ ++ return surface_sam_ssh_rqst(&rqst, NULL); ++} ++ ++inline static int shps_dtx_latch_lock(void) ++{ ++ return dtx_cmd_simple(SAM_DTX_CID_LATCH_LOCK); ++} ++ ++inline static int shps_dtx_latch_unlock(void) ++{ ++ return dtx_cmd_simple(SAM_DTX_CID_LATCH_UNLOCK); ++} ++ ++ ++static int shps_dgpu_dsm_get_pci_addr(struct platform_device *pdev, const char* entry) ++{ ++ acpi_handle handle = ACPI_HANDLE(&pdev->dev); ++ union acpi_object *result; ++ union acpi_object *e0; ++ union acpi_object *e1; ++ union acpi_object *e2; ++ u64 device_addr = 0; ++ u8 bus, dev, fun; ++ int i; ++ ++ result = acpi_evaluate_dsm_typed(handle, &SHPS_DSM_UUID, SHPS_DSM_REVISION, ++ SHPS_DSM_GPU_ADDRS, NULL, ACPI_TYPE_PACKAGE); ++ ++ if (IS_ERR_OR_NULL(result)) ++ return result ? PTR_ERR(result) : -EIO; ++ ++ // three entries per device: name, address, ++ for (i = 0; i + 2 < result->package.count; i += 3) { ++ e0 = &result->package.elements[i]; ++ e1 = &result->package.elements[i + 1]; ++ e2 = &result->package.elements[i + 2]; ++ ++ if (e0->type != ACPI_TYPE_STRING) { ++ ACPI_FREE(result); ++ return -EIO; ++ } ++ ++ if (e1->type != ACPI_TYPE_INTEGER) { ++ ACPI_FREE(result); ++ return -EIO; ++ } ++ ++ if (e2->type != ACPI_TYPE_INTEGER) { ++ ACPI_FREE(result); ++ return -EIO; ++ } ++ ++ if (strncmp(e0->string.pointer, entry, 64) == 0) ++ device_addr = e1->integer.value; ++ } ++ ++ ACPI_FREE(result); ++ if (device_addr == 0) ++ return -ENODEV; ++ ++ // convert address ++ bus = (device_addr & 0x0FF00000) >> 20; ++ dev = (device_addr & 0x000F8000) >> 15; ++ fun = (device_addr & 0x00007000) >> 12; ++ ++ return bus << 8 | PCI_DEVFN(dev, fun); ++} ++ ++static struct pci_dev *shps_dgpu_dsm_get_pci_dev(struct platform_device *pdev, const char* entry) ++{ ++ struct pci_dev *dev; ++ int addr; ++ ++ addr = shps_dgpu_dsm_get_pci_addr(pdev, entry); ++ if (addr < 0) ++ return ERR_PTR(addr); ++ ++ dev = pci_get_domain_bus_and_slot(0, (addr & 0xFF00) >> 8, addr & 0xFF); ++ return dev ? dev : ERR_PTR(-ENODEV); ++} ++ ++ ++static int shps_dgpu_dsm_get_power_unlocked(struct platform_device *pdev) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ struct gpio_desc *gpio = drvdata->gpio_dgpu_power; ++ int status; ++ ++ status = gpiod_get_value_cansleep(gpio); ++ if (status < 0) ++ return status; ++ ++ return status == 0 ? SHPS_DGPU_POWER_OFF : SHPS_DGPU_POWER_ON; ++} ++ ++static int shps_dgpu_dsm_get_power(struct platform_device *pdev) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ mutex_lock(&drvdata->lock); ++ status = shps_dgpu_dsm_get_power_unlocked(pdev); ++ mutex_unlock(&drvdata->lock); ++ ++ return status; ++} ++ ++static int __shps_dgpu_dsm_set_power_unlocked(struct platform_device *pdev, enum shps_dgpu_power power) ++{ ++ acpi_handle handle = ACPI_HANDLE(&pdev->dev); ++ union acpi_object *result; ++ union acpi_object param; ++ ++ dev_info(&pdev->dev, "setting dGPU direct power to \'%s\'\n", shps_dgpu_power_str(power)); ++ ++ param.type = ACPI_TYPE_INTEGER; ++ param.integer.value = power == SHPS_DGPU_POWER_ON; ++ ++ result = acpi_evaluate_dsm_typed(handle, &SHPS_DSM_UUID, SHPS_DSM_REVISION, ++ SHPS_DSM_GPU_POWER, ¶m, ACPI_TYPE_BUFFER); ++ ++ if (IS_ERR_OR_NULL(result)) ++ return result ? PTR_ERR(result) : -EIO; ++ ++ // check for the expected result ++ if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { ++ ACPI_FREE(result); ++ return -EIO; ++ } ++ ++ ACPI_FREE(result); ++ return 0; ++} ++ ++static int shps_dgpu_dsm_set_power_unlocked(struct platform_device *pdev, enum shps_dgpu_power power) ++{ ++ int status; ++ ++ if (power != SHPS_DGPU_POWER_ON && power != SHPS_DGPU_POWER_OFF) ++ return -EINVAL; ++ ++ status = shps_dgpu_dsm_get_power_unlocked(pdev); ++ if (status < 0) ++ return status; ++ if (status == power) ++ return 0; ++ ++ return __shps_dgpu_dsm_set_power_unlocked(pdev, power); ++} ++ ++static int shps_dgpu_dsm_set_power(struct platform_device *pdev, enum shps_dgpu_power power) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ mutex_lock(&drvdata->lock); ++ status = shps_dgpu_dsm_set_power_unlocked(pdev, power); ++ mutex_unlock(&drvdata->lock); ++ ++ return status; ++} ++ ++ ++static bool shps_rp_link_up(struct pci_dev *rp) ++{ ++ u16 lnksta = 0, sltsta = 0; ++ ++ pcie_capability_read_word(rp, PCI_EXP_LNKSTA, &lnksta); ++ pcie_capability_read_word(rp, PCI_EXP_SLTSTA, &sltsta); ++ ++ return (lnksta & PCI_EXP_LNKSTA_DLLLA) || (sltsta & PCI_EXP_SLTSTA_PDS); ++} ++ ++ ++static int shps_dgpu_rp_get_power_unlocked(struct platform_device *pdev) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ struct pci_dev *rp = drvdata->dgpu_root_port; ++ ++ if (rp->current_state == PCI_D3hot || rp->current_state == PCI_D3cold) ++ return SHPS_DGPU_POWER_OFF; ++ else if (rp->current_state == PCI_UNKNOWN || rp->current_state == PCI_POWER_ERROR) ++ return SHPS_DGPU_POWER_UNKNOWN; ++ else ++ return SHPS_DGPU_POWER_ON; ++} ++ ++static int shps_dgpu_rp_get_power(struct platform_device *pdev) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ mutex_lock(&drvdata->lock); ++ status = shps_dgpu_rp_get_power_unlocked(pdev); ++ mutex_unlock(&drvdata->lock); ++ ++ return status; ++} ++ ++static int __shps_dgpu_rp_set_power_unlocked(struct platform_device *pdev, enum shps_dgpu_power power) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ struct pci_dev *rp = drvdata->dgpu_root_port; ++ int status, i; ++ ++ dev_info(&pdev->dev, "setting dGPU power state to \'%s\'\n", shps_dgpu_power_str(power)); ++ ++ dbg_dump_drvsta(pdev, "__shps_dgpu_rp_set_power_unlocked.1"); ++ if (power == SHPS_DGPU_POWER_ON) { ++ set_bit(SHPS_STATE_BIT_RPPWRON_SYNC, &drvdata->state); ++ pci_set_power_state(rp, PCI_D0); ++ ++ if (drvdata->dgpu_root_port_state) ++ pci_load_and_free_saved_state(rp, &drvdata->dgpu_root_port_state); ++ ++ pci_restore_state(rp); ++ ++ if (!pci_is_enabled(rp)) ++ pci_enable_device(rp); ++ ++ pci_set_master(rp); ++ clear_bit(SHPS_STATE_BIT_RPPWRON_SYNC, &drvdata->state); ++ ++ set_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); ++ } else { ++ if (!drvdata->dgpu_root_port_state) { ++ pci_save_state(rp); ++ drvdata->dgpu_root_port_state = pci_store_saved_state(rp); ++ } ++ ++ /* ++ * To properly update the hot-plug system we need to "remove" the dGPU ++ * before disabling it and sending it to D3cold. Following this, we ++ * need to wait for the link and slot status to actually change. ++ */ ++ status = shps_dgpu_dsm_set_power_unlocked(pdev, SHPS_DGPU_POWER_OFF); ++ if (status) ++ return status; ++ ++ for (i = 0; i < 20 && shps_rp_link_up(rp); i++) ++ msleep(50); ++ ++ if (shps_rp_link_up(rp)) ++ dev_err(&pdev->dev, "dGPU removal via DSM timed out\n"); ++ ++ pci_clear_master(rp); ++ ++ if (pci_is_enabled(rp)) ++ pci_disable_device(rp); ++ ++ pci_set_power_state(rp, PCI_D3cold); ++ ++ clear_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); ++ } ++ dbg_dump_drvsta(pdev, "__shps_dgpu_rp_set_power_unlocked.2"); ++ ++ return 0; ++} ++ ++static int shps_dgpu_rp_set_power_unlocked(struct platform_device *pdev, enum shps_dgpu_power power) ++{ ++ int status; ++ ++ if (power != SHPS_DGPU_POWER_ON && power != SHPS_DGPU_POWER_OFF) ++ return -EINVAL; ++ ++ status = shps_dgpu_rp_get_power_unlocked(pdev); ++ if (status < 0) ++ return status; ++ if (status == power) ++ return 0; ++ ++ return __shps_dgpu_rp_set_power_unlocked(pdev, power); ++} ++ ++static int shps_dgpu_rp_set_power(struct platform_device *pdev, enum shps_dgpu_power power) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ mutex_lock(&drvdata->lock); ++ status = shps_dgpu_rp_set_power_unlocked(pdev, power); ++ mutex_unlock(&drvdata->lock); ++ ++ return status; ++} ++ ++ ++static int shps_dgpu_set_power(struct platform_device *pdev, enum shps_dgpu_power power) ++{ ++ int status; ++ ++ if (!param_dtx_latch) ++ return shps_dgpu_rp_set_power(pdev, power); ++ ++ if (power == SHPS_DGPU_POWER_ON) { ++ status = shps_dtx_latch_lock(); ++ if (status) ++ return status; ++ ++ status = shps_dgpu_rp_set_power(pdev, power); ++ if (status) ++ shps_dtx_latch_unlock(); ++ ++ return status; ++ } else { ++ status = shps_dgpu_rp_set_power(pdev, power); ++ if (status) ++ return status; ++ ++ return shps_dtx_latch_unlock(); ++ } ++} ++ ++ ++static int shps_dgpu_is_present(struct platform_device *pdev) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ return gpiod_get_value_cansleep(drvdata->gpio_dgpu_presence); ++} ++ ++ ++static ssize_t dgpu_power_show(struct device *dev, struct device_attribute *attr, char *data) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ int power = shps_dgpu_rp_get_power(pdev); ++ ++ if (power < 0) ++ return power; ++ ++ return sprintf(data, "%s\n", shps_dgpu_power_str(power)); ++} ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *data, size_t count) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ enum shps_dgpu_power power; ++ bool b = false; ++ int status; ++ ++ status = kstrtobool(data, &b); ++ if (status) ++ return status; ++ ++ status = shps_dgpu_is_present(pdev); ++ if (status <= 0) ++ return status < 0 ? status : -EPERM; ++ ++ power = b ? SHPS_DGPU_POWER_ON : SHPS_DGPU_POWER_OFF; ++ status = shps_dgpu_set_power(pdev, power); ++ ++ return status < 0 ? status : count; ++} ++ ++static ssize_t dgpu_power_dsm_show(struct device *dev, struct device_attribute *attr, char *data) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ int power = shps_dgpu_dsm_get_power(pdev); ++ ++ if (power < 0) ++ return power; ++ ++ return sprintf(data, "%s\n", shps_dgpu_power_str(power)); ++} ++ ++static ssize_t dgpu_power_dsm_store(struct device *dev, struct device_attribute *attr, ++ const char *data, size_t count) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ enum shps_dgpu_power power; ++ bool b = false; ++ int status; ++ ++ status = kstrtobool(data, &b); ++ if (status) ++ return status; ++ ++ status = shps_dgpu_is_present(pdev); ++ if (status <= 0) ++ return status < 0 ? status : -EPERM; ++ ++ power = b ? SHPS_DGPU_POWER_ON : SHPS_DGPU_POWER_OFF; ++ status = shps_dgpu_dsm_set_power(pdev, power); ++ ++ return status < 0 ? status : count; ++} ++ ++static DEVICE_ATTR_RW(dgpu_power); ++static DEVICE_ATTR_RW(dgpu_power_dsm); ++ ++static struct attribute *shps_power_attrs[] = { ++ &dev_attr_dgpu_power.attr, ++ &dev_attr_dgpu_power_dsm.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(shps_power); ++ ++ ++static void dbg_dump_power_states(struct platform_device *pdev, const char *prefix) ++{ ++ enum shps_dgpu_power power_dsm; ++ enum shps_dgpu_power power_rp; ++ int status; ++ ++ status = shps_dgpu_rp_get_power_unlocked(pdev); ++ if (status < 0) ++ dev_err(&pdev->dev, "%s: failed to get root-port power state: %d\n", prefix, status); ++ power_rp = status; ++ ++ status = shps_dgpu_rp_get_power_unlocked(pdev); ++ if (status < 0) ++ dev_err(&pdev->dev, "%s: failed to get direct power state: %d\n", prefix, status); ++ power_dsm = status; ++ ++ dev_dbg(&pdev->dev, "%s: root-port power state: %d\n", prefix, power_rp); ++ dev_dbg(&pdev->dev, "%s: direct power state: %d\n", prefix, power_dsm); ++} ++ ++static void dbg_dump_pciesta(struct platform_device *pdev, const char *prefix) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ struct pci_dev *rp = drvdata->dgpu_root_port; ++ u16 lnksta, lnksta2, sltsta, sltsta2; ++ ++ pcie_capability_read_word(rp, PCI_EXP_LNKSTA, &lnksta); ++ pcie_capability_read_word(rp, PCI_EXP_LNKSTA2, &lnksta2); ++ pcie_capability_read_word(rp, PCI_EXP_SLTSTA, &sltsta); ++ pcie_capability_read_word(rp, PCI_EXP_SLTSTA2, &sltsta2); ++ ++ dev_dbg(&pdev->dev, "%s: LNKSTA: 0x%04x", prefix, lnksta); ++ dev_dbg(&pdev->dev, "%s: LNKSTA2: 0x%04x", prefix, lnksta2); ++ dev_dbg(&pdev->dev, "%s: SLTSTA: 0x%04x", prefix, sltsta); ++ dev_dbg(&pdev->dev, "%s: SLTSTA2: 0x%04x", prefix, sltsta2); ++} ++ ++static void dbg_dump_drvsta(struct platform_device *pdev, const char *prefix) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ struct pci_dev *rp = drvdata->dgpu_root_port; ++ ++ dev_dbg(&pdev->dev, "%s: RP power: %d", prefix, rp->current_state); ++ dev_dbg(&pdev->dev, "%s: RP state saved: %d", prefix, rp->state_saved); ++ dev_dbg(&pdev->dev, "%s: RP state stored: %d", prefix, !!drvdata->dgpu_root_port_state); ++ dev_dbg(&pdev->dev, "%s: RP enabled: %d", prefix, atomic_read(&rp->enable_cnt)); ++ dev_dbg(&pdev->dev, "%s: RP mastered: %d", prefix, rp->is_busmaster); ++} ++ ++ ++static int shps_pm_prepare(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ bool pwrtgt; ++ int status = 0; ++ ++ dbg_dump_power_states(pdev, "shps_pm_prepare"); ++ ++ if (param_dgpu_power_susp != SHPS_DGPU_MP_POWER_ASIS) { ++ pwrtgt = test_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); ++ ++ status = shps_dgpu_set_power(pdev, param_dgpu_power_susp); ++ if (status) { ++ dev_err(&pdev->dev, "failed to power %s dGPU: %d\n", ++ param_dgpu_power_susp == SHPS_DGPU_MP_POWER_OFF ? "off" : "on", ++ status); ++ return status; ++ } ++ ++ if (pwrtgt) ++ set_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); ++ else ++ clear_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); ++ } ++ ++ return 0; ++} ++ ++static void shps_pm_complete(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ dbg_dump_power_states(pdev, "shps_pm_complete"); ++ dbg_dump_pciesta(pdev, "shps_pm_complete"); ++ dbg_dump_drvsta(pdev, "shps_pm_complete.1"); ++ ++ // update power target, dGPU may have been detached while suspended ++ status = shps_dgpu_is_present(pdev); ++ if (status < 0) { ++ dev_err(&pdev->dev, "failed to get dGPU presence: %d\n", status); ++ return; ++ } else if (status == 0) { ++ clear_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); ++ } ++ ++ /* ++ * During resume, the PCIe core will power on the root-port, which in turn ++ * will power on the dGPU. Most of the state synchronization is already ++ * handled via the SAN RQSG handler, so it is in a fully consistent ++ * on-state here. If requested, turn it off here. ++ * ++ * As there seem to be some synchronization issues turning off the dGPU ++ * directly after the power-on SAN RQSG notification during the resume ++ * process, let's do this here. ++ * ++ * TODO/FIXME: ++ * This does not combat unhandled power-ons when the device is not fully ++ * resumed, i.e. re-suspended before shps_pm_complete is called. Those ++ * should normally not be an issue, but the dGPU does get hot even though ++ * it is suspended, so ideally we want to keep it off. ++ */ ++ if (!test_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state)) { ++ status = shps_dgpu_set_power(pdev, SHPS_DGPU_POWER_OFF); ++ if (status) ++ dev_err(&pdev->dev, "failed to power-off dGPU: %d\n", status); ++ } ++ ++ dbg_dump_drvsta(pdev, "shps_pm_complete.2"); ++} ++ ++static int shps_pm_suspend(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ if (device_may_wakeup(dev)) { ++ status = enable_irq_wake(drvdata->irq_base_presence); ++ if (status) ++ return status; ++ ++ set_bit(SHPS_STATE_BIT_WAKE_ENABLED, &drvdata->state); ++ } ++ ++ return 0; ++} ++ ++static int shps_pm_resume(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status = 0; ++ ++ if (test_and_clear_bit(SHPS_STATE_BIT_WAKE_ENABLED, &drvdata->state)) { ++ status = disable_irq_wake(drvdata->irq_base_presence); ++ } ++ ++ return status; ++} ++ ++static void shps_shutdown(struct platform_device *pdev) ++{ ++ int status; ++ ++ /* ++ * Turn on dGPU before shutting down. This allows the core drivers to ++ * properly shut down the device. If we don't do this, the pcieport driver ++ * will complain that the device has already been disabled. ++ */ ++ status = shps_dgpu_set_power(pdev, SHPS_DGPU_POWER_ON); ++ if (status) ++ dev_err(&pdev->dev, "failed to turn on dGPU: %d\n", status); ++} ++ ++static int shps_dgpu_detached(struct platform_device *pdev) ++{ ++ dbg_dump_power_states(pdev, "shps_dgpu_detached"); ++ return shps_dgpu_set_power(pdev, SHPS_DGPU_POWER_OFF); ++} ++ ++static int shps_dgpu_attached(struct platform_device *pdev) ++{ ++ dbg_dump_power_states(pdev, "shps_dgpu_attached"); ++ return 0; ++} ++ ++static int shps_dgpu_powered_on(struct platform_device *pdev) ++{ ++ /* ++ * This function gets called directly after a power-state transition of ++ * the dGPU root port out of D3cold state, indicating a power-on of the ++ * dGPU. Specifically, this function is called from the RQSG handler of ++ * SAN, invoked by the ACPI _ON method of the dGPU root port. This means ++ * that this function is run inside `pci_set_power_state(rp, ...)` ++ * syncrhonously and thus returns before the `pci_set_power_state` call ++ * does. ++ * ++ * `pci_set_power_state` may either be called by us or when the PCI ++ * subsystem decides to power up the root port (e.g. during resume). Thus ++ * we should use this function to ensure that the dGPU and root port ++ * states are consistent when an unexpected power-up is encountered. ++ */ ++ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ struct pci_dev *rp = drvdata->dgpu_root_port; ++ int status; ++ ++ dbg_dump_drvsta(pdev, "shps_dgpu_powered_on.1"); ++ ++ // if we caused the root port to power-on, return ++ if (test_bit(SHPS_STATE_BIT_RPPWRON_SYNC, &drvdata->state)) ++ return 0; ++ ++ // if dGPU is not present, force power-target to off and return ++ status = shps_dgpu_is_present(pdev); ++ if (status == 0) ++ clear_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); ++ if (status <= 0) ++ return status; ++ ++ mutex_lock(&drvdata->lock); ++ ++ dbg_dump_power_states(pdev, "shps_dgpu_powered_on.1"); ++ dbg_dump_pciesta(pdev, "shps_dgpu_powered_on.1"); ++ if (drvdata->dgpu_root_port_state) ++ pci_load_and_free_saved_state(rp, &drvdata->dgpu_root_port_state); ++ pci_restore_state(rp); ++ if (!pci_is_enabled(rp)) ++ pci_enable_device(rp); ++ pci_set_master(rp); ++ dbg_dump_drvsta(pdev, "shps_dgpu_powered_on.2"); ++ dbg_dump_power_states(pdev, "shps_dgpu_powered_on.2"); ++ dbg_dump_pciesta(pdev, "shps_dgpu_powered_on.2"); ++ ++ mutex_unlock(&drvdata->lock); ++ ++ if (!test_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state)) { ++ dev_warn(&pdev->dev, "unexpected dGPU power-on detected"); ++ // TODO: schedule state re-check and update ++ } ++ ++ return 0; ++} ++ ++ ++static int shps_dgpu_handle_rqsg(struct surface_sam_san_rqsg *rqsg, void *data) ++{ ++ struct platform_device *pdev = data; ++ ++ if (rqsg->tc == SAM_DGPU_TC && rqsg->cid == SAM_DGPU_CID_POWERON) ++ return shps_dgpu_powered_on(pdev); ++ ++ dev_warn(&pdev->dev, "unimplemented dGPU request: RQSG(0x%02x, 0x%02x, 0x%02x)", ++ rqsg->tc, rqsg->cid, rqsg->iid); ++ return 0; ++} ++ ++static irqreturn_t shps_dgpu_presence_irq(int irq, void *data) ++{ ++ struct platform_device *pdev = data; ++ bool dgpu_present; ++ int status; ++ ++ status = shps_dgpu_is_present(pdev); ++ if (status < 0) { ++ dev_err(&pdev->dev, "failed to check physical dGPU presence: %d\n", status); ++ return IRQ_HANDLED; ++ } ++ ++ dgpu_present = status != 0; ++ dev_info(&pdev->dev, "dGPU physically %s\n", dgpu_present ? "attached" : "detached"); ++ ++ if (dgpu_present) ++ status = shps_dgpu_attached(pdev); ++ else ++ status = shps_dgpu_detached(pdev); ++ ++ if (status) ++ dev_err(&pdev->dev, "error handling dGPU interrupt: %d\n", status); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t shps_base_presence_irq(int irq, void *data) ++{ ++ return IRQ_HANDLED; // nothing to do, just wake ++} ++ ++ ++static int shps_gpios_setup(struct platform_device *pdev) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ struct gpio_desc *gpio_dgpu_power; ++ struct gpio_desc *gpio_dgpu_presence; ++ struct gpio_desc *gpio_base_presence; ++ int status; ++ ++ // get GPIOs ++ gpio_dgpu_power = devm_gpiod_get(&pdev->dev, "dgpu_power", GPIOD_IN); ++ if (IS_ERR(gpio_dgpu_power)) { ++ status = PTR_ERR(gpio_dgpu_power); ++ goto err_out; ++ } ++ ++ gpio_dgpu_presence = devm_gpiod_get(&pdev->dev, "dgpu_presence", GPIOD_IN); ++ if (IS_ERR(gpio_dgpu_presence)) { ++ status = PTR_ERR(gpio_dgpu_presence); ++ goto err_out; ++ } ++ ++ gpio_base_presence = devm_gpiod_get(&pdev->dev, "base_presence", GPIOD_IN); ++ if (IS_ERR(gpio_base_presence)) { ++ status = PTR_ERR(gpio_base_presence); ++ goto err_out; ++ } ++ ++ // export GPIOs ++ status = gpiod_export(gpio_dgpu_power, false); ++ if (status) ++ goto err_out; ++ ++ status = gpiod_export(gpio_dgpu_presence, false); ++ if (status) ++ goto err_export_dgpu_presence; ++ ++ status = gpiod_export(gpio_base_presence, false); ++ if (status) ++ goto err_export_base_presence; ++ ++ // create sysfs links ++ status = gpiod_export_link(&pdev->dev, "gpio-dgpu_power", gpio_dgpu_power); ++ if (status) ++ goto err_link_dgpu_power; ++ ++ status = gpiod_export_link(&pdev->dev, "gpio-dgpu_presence", gpio_dgpu_presence); ++ if (status) ++ goto err_link_dgpu_presence; ++ ++ status = gpiod_export_link(&pdev->dev, "gpio-base_presence", gpio_base_presence); ++ if (status) ++ goto err_link_base_presence; ++ ++ drvdata->gpio_dgpu_power = gpio_dgpu_power; ++ drvdata->gpio_dgpu_presence = gpio_dgpu_presence; ++ drvdata->gpio_base_presence = gpio_base_presence; ++ return 0; ++ ++err_link_base_presence: ++ sysfs_remove_link(&pdev->dev.kobj, "gpio-dgpu_presence"); ++err_link_dgpu_presence: ++ sysfs_remove_link(&pdev->dev.kobj, "gpio-dgpu_power"); ++err_link_dgpu_power: ++ gpiod_unexport(gpio_base_presence); ++err_export_base_presence: ++ gpiod_unexport(gpio_dgpu_presence); ++err_export_dgpu_presence: ++ gpiod_unexport(gpio_dgpu_power); ++err_out: ++ return status; ++} ++ ++static void shps_gpios_remove(struct platform_device *pdev) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ ++ sysfs_remove_link(&pdev->dev.kobj, "gpio-base_presence"); ++ sysfs_remove_link(&pdev->dev.kobj, "gpio-dgpu_presence"); ++ sysfs_remove_link(&pdev->dev.kobj, "gpio-dgpu_power"); ++ gpiod_unexport(drvdata->gpio_base_presence); ++ gpiod_unexport(drvdata->gpio_dgpu_presence); ++ gpiod_unexport(drvdata->gpio_dgpu_power); ++} ++ ++static int shps_gpios_setup_irq(struct platform_device *pdev) ++{ ++ const int irqf_dgpu = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; ++ const int irqf_base = IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ status = gpiod_to_irq(drvdata->gpio_base_presence); ++ if (status < 0) ++ return status; ++ drvdata->irq_base_presence = status; ++ ++ status = gpiod_to_irq(drvdata->gpio_dgpu_presence); ++ if (status < 0) ++ return status; ++ drvdata->irq_dgpu_presence = status; ++ ++ status = request_irq(drvdata->irq_base_presence, ++ shps_base_presence_irq, irqf_base, ++ "shps_base_presence_irq", pdev); ++ if (status) ++ return status; ++ ++ status = request_threaded_irq(drvdata->irq_dgpu_presence, ++ NULL, shps_dgpu_presence_irq, irqf_dgpu, ++ "shps_dgpu_presence_irq", pdev); ++ if (status) { ++ free_irq(drvdata->irq_base_presence, pdev); ++ return status; ++ } ++ ++ return 0; ++} ++ ++static void shps_gpios_remove_irq(struct platform_device *pdev) ++{ ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ ++ free_irq(drvdata->irq_base_presence, pdev); ++ free_irq(drvdata->irq_dgpu_presence, pdev); ++} ++ ++static int shps_probe(struct platform_device *pdev) ++{ ++ struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); ++ struct shps_driver_data *drvdata; ++ struct device_link *link; ++ int power, status; ++ ++ if (gpiod_count(&pdev->dev, NULL) < 0) ++ return -ENODEV; ++ ++ // link to SSH ++ status = surface_sam_ssh_consumer_register(&pdev->dev); ++ if (status) { ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ } ++ ++ // link to SAN ++ status = surface_sam_san_consumer_register(&pdev->dev, 0); ++ if (status) { ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ } ++ ++ status = acpi_dev_add_driver_gpios(shps_dev, shps_acpi_gpios); ++ if (status) ++ return status; ++ ++ drvdata = kzalloc(sizeof(struct shps_driver_data), GFP_KERNEL); ++ if (!drvdata) { ++ status = -ENOMEM; ++ goto err_drvdata; ++ } ++ mutex_init(&drvdata->lock); ++ platform_set_drvdata(pdev, drvdata); ++ ++ drvdata->dgpu_root_port = shps_dgpu_dsm_get_pci_dev(pdev, SHPS_DSM_GPU_ADDRS_RP); ++ if (IS_ERR(drvdata->dgpu_root_port)) { ++ status = PTR_ERR(drvdata->dgpu_root_port); ++ goto err_rp_lookup; ++ } ++ ++ status = shps_gpios_setup(pdev); ++ if (status) ++ goto err_gpio; ++ ++ status = shps_gpios_setup_irq(pdev); ++ if (status) ++ goto err_gpio_irqs; ++ ++ status = device_add_groups(&pdev->dev, shps_power_groups); ++ if (status) ++ goto err_devattr; ++ ++ link = device_link_add(&pdev->dev, &drvdata->dgpu_root_port->dev, ++ DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER); ++ if (!link) ++ goto err_devlink; ++ ++ surface_sam_san_set_rqsg_handler(shps_dgpu_handle_rqsg, pdev); ++ ++ // if dGPU is not present turn-off root-port, else obey module param ++ status = shps_dgpu_is_present(pdev); ++ if (status < 0) ++ goto err_devlink; ++ ++ power = status == 0 ? SHPS_DGPU_POWER_OFF : param_dgpu_power_init; ++ if (power != SHPS_DGPU_MP_POWER_ASIS) { ++ status = shps_dgpu_set_power(pdev, power); ++ if (status) ++ goto err_devlink; ++ } ++ ++ device_init_wakeup(&pdev->dev, true); ++ return 0; ++ ++err_devlink: ++ device_remove_groups(&pdev->dev, shps_power_groups); ++err_devattr: ++ shps_gpios_remove_irq(pdev); ++err_gpio_irqs: ++ shps_gpios_remove(pdev); ++err_gpio: ++ pci_dev_put(drvdata->dgpu_root_port); ++err_rp_lookup: ++ platform_set_drvdata(pdev, NULL); ++ kfree(drvdata); ++err_drvdata: ++ acpi_dev_remove_driver_gpios(shps_dev); ++ return status; ++} ++ ++static int shps_remove(struct platform_device *pdev) ++{ ++ struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); ++ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ if (param_dgpu_power_exit != SHPS_DGPU_MP_POWER_ASIS) { ++ status = shps_dgpu_set_power(pdev, param_dgpu_power_exit); ++ if (status) ++ dev_err(&pdev->dev, "failed to set dGPU power state: %d\n", status); ++ } ++ ++ device_set_wakeup_capable(&pdev->dev, false); ++ surface_sam_san_set_rqsg_handler(NULL, NULL); ++ device_remove_groups(&pdev->dev, shps_power_groups); ++ shps_gpios_remove_irq(pdev); ++ shps_gpios_remove(pdev); ++ pci_dev_put(drvdata->dgpu_root_port); ++ platform_set_drvdata(pdev, NULL); ++ kfree(drvdata); ++ ++ acpi_dev_remove_driver_gpios(shps_dev); ++ return 0; ++} ++ ++ ++static const struct dev_pm_ops shps_pm_ops = { ++ .prepare = shps_pm_prepare, ++ .complete = shps_pm_complete, ++ .suspend = shps_pm_suspend, ++ .resume = shps_pm_resume, ++}; ++ ++static const struct acpi_device_id shps_acpi_match[] = { ++ { "MSHW0153", 0 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, shps_acpi_match); ++ ++struct platform_driver surface_sam_hps = { ++ .probe = shps_probe, ++ .remove = shps_remove, ++ .shutdown = shps_shutdown, ++ .driver = { ++ .name = "surface_dgpu_hps", ++ .acpi_match_table = ACPI_PTR(shps_acpi_match), ++ .pm = &shps_pm_ops, ++ }, ++}; ++module_platform_driver(surface_sam_hps); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface Hot-Plug System (HPS) and dGPU power-state Driver for Surface Book 2"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_san.c b/drivers/platform/x86/surface_sam/surface_sam_san.c +new file mode 100644 +index 000000000000..aa0cfc4262be +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_san.c +@@ -0,0 +1,901 @@ ++/* ++ * Surface ACPI Notify (SAN) and ACPI integration driver for SAM. ++ * Translates communication from ACPI to SSH and back. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "surface_sam_ssh.h" ++#include "surface_sam_san.h" ++ ++ ++#define SAN_RQST_RETRY 5 ++ ++#define SAN_DSM_REVISION 0 ++#define SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT 0x09 ++ ++static const guid_t SAN_DSM_UUID = ++ GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, ++ 0x48, 0x7c, 0x91, 0xab, 0x3c); ++ ++#define SAM_EVENT_DELAY_PWR_ADAPTER msecs_to_jiffies(5000) ++#define SAM_EVENT_DELAY_PWR_BST msecs_to_jiffies(2500) ++ ++#define SAM_EVENT_PWR_TC 0x02 ++#define SAM_EVENT_PWR_RQID 0x0002 ++#define SAM_EVENT_PWR_CID_BIX 0x15 ++#define SAM_EVENT_PWR_CID_BST 0x16 ++#define SAM_EVENT_PWR_CID_ADAPTER 0x17 ++#define SAM_EVENT_PWR_CID_DPTF 0x4f ++ ++#define SAM_EVENT_TEMP_TC 0x03 ++#define SAM_EVENT_TEMP_RQID 0x0003 ++#define SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT 0x0b ++ ++#define SAN_RQST_TAG "surface_sam_san: rqst: " ++#define SAN_RQSG_TAG "surface_sam_san: rqsg: " ++ ++#define SAN_QUIRK_BASE_STATE_DELAY 1000 ++ ++ ++struct san_acpi_consumer { ++ char *path; ++ bool required; ++ u32 flags; ++}; ++ ++struct san_opreg_context { ++ struct acpi_connection_info connection; ++ struct device *dev; ++}; ++ ++struct san_consumer_link { ++ const struct san_acpi_consumer *properties; ++ struct device_link *link; ++}; ++ ++struct san_consumers { ++ u32 num; ++ struct san_consumer_link *links; ++}; ++ ++struct san_drvdata { ++ struct san_opreg_context opreg_ctx; ++ struct san_consumers consumers; ++ bool has_power_events; ++}; ++ ++struct gsb_data_in { ++ u8 cv; ++} __packed; ++ ++struct gsb_data_rqsx { ++ u8 cv; // command value (should be 0x01 or 0x03) ++ u8 tc; // target controller ++ u8 tid; // expected to be 0x01, could be revision ++ u8 iid; // target sub-controller (e.g. primary vs. secondary battery) ++ u8 snc; // expect-response-flag ++ u8 cid; // command ID ++ u8 cdl; // payload length ++ u8 _pad; // padding ++ u8 pld[0]; // payload ++} __packed; ++ ++struct gsb_data_etwl { ++ u8 cv; // command value (should be 0x02) ++ u8 etw3; // ? ++ u8 etw4; // ? ++ u8 msg[0]; // error message (ASCIIZ) ++} __packed; ++ ++struct gsb_data_out { ++ u8 status; // _SSH communication status ++ u8 len; // _SSH payload length ++ u8 pld[0]; // _SSH payload ++} __packed; ++ ++union gsb_buffer_data { ++ struct gsb_data_in in; // common input ++ struct gsb_data_rqsx rqsx; // RQSX input ++ struct gsb_data_etwl etwl; // ETWL input ++ struct gsb_data_out out; // output ++}; ++ ++struct gsb_buffer { ++ u8 status; // GSB AttribRawProcess status ++ u8 len; // GSB AttribRawProcess length ++ union gsb_buffer_data data; ++} __packed; ++ ++ ++enum san_pwr_event { ++ SAN_PWR_EVENT_BAT1_STAT = 0x03, ++ SAN_PWR_EVENT_BAT1_INFO = 0x04, ++ SAN_PWR_EVENT_ADP1_STAT = 0x05, ++ SAN_PWR_EVENT_ADP1_INFO = 0x06, ++ SAN_PWR_EVENT_BAT2_STAT = 0x07, ++ SAN_PWR_EVENT_BAT2_INFO = 0x08, ++}; ++ ++ ++static int sam_san_default_rqsg_handler(struct surface_sam_san_rqsg *rqsg, void *data); ++ ++struct sam_san_rqsg_if { ++ struct mutex lock; ++ struct device *san_dev; ++ surface_sam_san_rqsg_handler_fn handler; ++ void *handler_data; ++}; ++ ++static struct sam_san_rqsg_if rqsg_if = { ++ .lock = __MUTEX_INITIALIZER(rqsg_if.lock), ++ .san_dev = NULL, ++ .handler = sam_san_default_rqsg_handler, ++ .handler_data = NULL, ++}; ++ ++int surface_sam_san_consumer_register(struct device *consumer, u32 flags) ++{ ++ const u32 valid = DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE; ++ int status; ++ ++ if ((flags | valid) != valid) ++ return -EINVAL; ++ ++ flags |= DL_FLAG_AUTOREMOVE_CONSUMER; ++ ++ mutex_lock(&rqsg_if.lock); ++ if (rqsg_if.san_dev) ++ status = device_link_add(consumer, rqsg_if.san_dev, flags) ? 0 : -EINVAL; ++ else ++ status = -ENXIO; ++ mutex_unlock(&rqsg_if.lock); ++ return status; ++} ++EXPORT_SYMBOL_GPL(surface_sam_san_consumer_register); ++ ++int surface_sam_san_set_rqsg_handler(surface_sam_san_rqsg_handler_fn fn, void *data) ++{ ++ int status = -EBUSY; ++ ++ mutex_lock(&rqsg_if.lock); ++ ++ if (rqsg_if.handler == sam_san_default_rqsg_handler || !fn) { ++ rqsg_if.handler = fn ? fn : sam_san_default_rqsg_handler; ++ rqsg_if.handler_data = data; ++ status = 0; ++ } ++ ++ mutex_unlock(&rqsg_if.lock); ++ return status; ++} ++EXPORT_SYMBOL_GPL(surface_sam_san_set_rqsg_handler); ++ ++int san_call_rqsg_handler(struct surface_sam_san_rqsg *rqsg) ++{ ++ int status; ++ ++ mutex_lock(&rqsg_if.lock); ++ status = rqsg_if.handler(rqsg, rqsg_if.handler_data); ++ mutex_unlock(&rqsg_if.lock); ++ ++ return status; ++} ++ ++static int sam_san_default_rqsg_handler(struct surface_sam_san_rqsg *rqsg, void *data) ++{ ++ pr_warn(SAN_RQSG_TAG "unhandled request: RQSG(0x%02x, 0x%02x, 0x%02x)\n", ++ rqsg->tc, rqsg->cid, rqsg->iid); ++ ++ return 0; ++} ++ ++ ++static int san_acpi_notify_power_event(struct device *dev, enum san_pwr_event event) ++{ ++ acpi_handle san = ACPI_HANDLE(dev); ++ union acpi_object *obj; ++ ++ dev_dbg(dev, "notify power event 0x%02x\n", event); ++ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, ++ (u8) event, NULL, ACPI_TYPE_BUFFER); ++ ++ if (IS_ERR_OR_NULL(obj)) { ++ return obj ? PTR_ERR(obj) : -ENXIO; ++ } ++ ++ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { ++ dev_err(dev, "got unexpected result from _DSM\n"); ++ return -EFAULT; ++ } ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int san_acpi_notify_sensor_trip_point(struct device *dev, u8 iid) ++{ ++ acpi_handle san = ACPI_HANDLE(dev); ++ union acpi_object *obj; ++ union acpi_object param; ++ ++ param.type = ACPI_TYPE_INTEGER; ++ param.integer.value = iid; ++ ++ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, ++ SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT, ++ ¶m, ACPI_TYPE_BUFFER); ++ ++ if (IS_ERR_OR_NULL(obj)) { ++ return obj ? PTR_ERR(obj) : -ENXIO; ++ } ++ ++ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { ++ dev_err(dev, "got unexpected result from _DSM\n"); ++ return -EFAULT; ++ } ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++ ++inline static int san_evt_power_adapter(struct device *dev, struct surface_sam_ssh_event *event) ++{ ++ int status; ++ ++ status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_ADP1_STAT); ++ if (status) { ++ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); ++ return status; ++ } ++ ++ /* ++ * Enusre that the battery states get updated correctly. ++ * When the battery is fully charged and an adapter is plugged in, it ++ * sometimes is not updated correctly, instead showing it as charging. ++ * Explicitly trigger battery updates to fix this. ++ */ ++ ++ status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_BAT1_STAT); ++ if (status) { ++ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); ++ return status; ++ } ++ ++ status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_BAT2_STAT); ++ if (status) { ++ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); ++ return status; ++ } ++ ++ return 0; ++} ++ ++inline static int san_evt_power_bix(struct device *dev, struct surface_sam_ssh_event *event) ++{ ++ enum san_pwr_event evcode; ++ int status; ++ ++ if (event->iid == 0x02) { ++ evcode = SAN_PWR_EVENT_BAT2_INFO; ++ } else { ++ evcode = SAN_PWR_EVENT_BAT1_INFO; ++ } ++ ++ status = san_acpi_notify_power_event(dev, evcode); ++ if (status) { ++ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); ++ return status; ++ } ++ ++ return 0; ++} ++ ++inline static int san_evt_power_bst(struct device *dev, struct surface_sam_ssh_event *event) ++{ ++ enum san_pwr_event evcode; ++ int status; ++ ++ if (event->iid == 0x02) { ++ evcode = SAN_PWR_EVENT_BAT2_STAT; ++ } else { ++ evcode = SAN_PWR_EVENT_BAT1_STAT; ++ } ++ ++ status = san_acpi_notify_power_event(dev, evcode); ++ if (status) { ++ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); ++ return status; ++ } ++ ++ return 0; ++} ++ ++static unsigned long san_evt_power_delay(struct surface_sam_ssh_event *event, void *data) ++{ ++ switch (event->cid) { ++ case SAM_EVENT_PWR_CID_ADAPTER: ++ /* ++ * Wait for battery state to update before signalling adapter change. ++ */ ++ return SAM_EVENT_DELAY_PWR_ADAPTER; ++ ++ case SAM_EVENT_PWR_CID_BST: ++ /* ++ * Ensure we do not miss anything important due to caching. ++ */ ++ return SAM_EVENT_DELAY_PWR_BST; ++ ++ case SAM_EVENT_PWR_CID_BIX: ++ case SAM_EVENT_PWR_CID_DPTF: ++ default: ++ return 0; ++ } ++} ++ ++static int san_evt_power(struct surface_sam_ssh_event *event, void *data) ++{ ++ struct device *dev = (struct device *)data; ++ ++ switch (event->cid) { ++ case SAM_EVENT_PWR_CID_BIX: ++ return san_evt_power_bix(dev, event); ++ ++ case SAM_EVENT_PWR_CID_BST: ++ return san_evt_power_bst(dev, event); ++ ++ case SAM_EVENT_PWR_CID_ADAPTER: ++ return san_evt_power_adapter(dev, event); ++ ++ case SAM_EVENT_PWR_CID_DPTF: ++ /* ++ * Ignored for now. ++ * This signals a change in Intel DPTF PMAX, and possibly other ++ * fields. Ignore for now as there is no corresponding _DSM call and ++ * DPTF is implemented via a separate INT3407 device. ++ * ++ * The payload of this event is: [u32 PMAX, unknown...]. ++ */ ++ return 0; ++ ++ default: ++ dev_warn(dev, "unhandled power event (cid = %x)\n", event->cid); ++ } ++ ++ return 0; ++} ++ ++ ++inline static int san_evt_thermal_notify(struct device *dev, struct surface_sam_ssh_event *event) ++{ ++ int status; ++ ++ status = san_acpi_notify_sensor_trip_point(dev, event->iid); ++ if (status) { ++ dev_err(dev, "error handling thermal event (cid = %x)\n", event->cid); ++ return status; ++ } ++ ++ return 0; ++} ++ ++static int san_evt_thermal(struct surface_sam_ssh_event *event, void *data) ++{ ++ struct device *dev = (struct device *)data; ++ ++ switch (event->cid) { ++ case SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT: ++ return san_evt_thermal_notify(dev, event); ++ ++ default: ++ dev_warn(dev, "unhandled thermal event (cid = %x)\n", event->cid); ++ } ++ ++ return 0; ++} ++ ++ ++static struct gsb_data_rqsx ++*san_validate_rqsx(struct device *dev, const char *type, struct gsb_buffer *buffer) ++{ ++ struct gsb_data_rqsx *rqsx = &buffer->data.rqsx; ++ ++ if (buffer->len < 8) { ++ dev_err(dev, "invalid %s package (len = %d)\n", ++ type, buffer->len); ++ return NULL; ++ } ++ ++ if (rqsx->cdl != buffer->len - 8) { ++ dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", ++ type, buffer->len, rqsx->cdl); ++ return NULL; ++ } ++ ++ if (rqsx->tid != 0x01) { ++ dev_warn(dev, "unsupported %s package (tid = 0x%02x)\n", ++ type, rqsx->tid); ++ return NULL; ++ } ++ ++ return rqsx; ++} ++ ++static acpi_status ++san_etwl(struct san_opreg_context *ctx, struct gsb_buffer *buffer) ++{ ++ struct gsb_data_etwl *etwl = &buffer->data.etwl; ++ ++ if (buffer->len < 3) { ++ dev_err(ctx->dev, "invalid ETWL package (len = %d)\n", buffer->len); ++ return AE_OK; ++ } ++ ++ dev_err(ctx->dev, "ETWL(0x%02x, 0x%02x): %.*s\n", ++ etwl->etw3, etwl->etw4, ++ buffer->len - 3, (char *)etwl->msg); ++ ++ // indicate success ++ buffer->status = 0x00; ++ buffer->len = 0x00; ++ ++ return AE_OK; ++} ++ ++static acpi_status ++san_rqst(struct san_opreg_context *ctx, struct gsb_buffer *buffer) ++{ ++ struct gsb_data_rqsx *gsb_rqst = san_validate_rqsx(ctx->dev, "RQST", buffer); ++ struct surface_sam_ssh_rqst rqst = {}; ++ struct surface_sam_ssh_buf result = {}; ++ int status = 0; ++ int try; ++ ++ if (!gsb_rqst) { ++ return AE_OK; ++ } ++ ++ rqst.tc = gsb_rqst->tc; ++ rqst.cid = gsb_rqst->cid; ++ rqst.iid = gsb_rqst->iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = gsb_rqst->snc; ++ rqst.cdl = gsb_rqst->cdl; ++ rqst.pld = &gsb_rqst->pld[0]; ++ ++ result.cap = SURFACE_SAM_SSH_MAX_RQST_RESPONSE; ++ result.len = 0; ++ result.data = kzalloc(result.cap, GFP_KERNEL); ++ ++ if (!result.data) { ++ return AE_NO_MEMORY; ++ } ++ ++ for (try = 0; try < SAN_RQST_RETRY; try++) { ++ if (try) { ++ dev_warn(ctx->dev, SAN_RQST_TAG "IO error occured, trying again\n"); ++ } ++ ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ if (status != -EIO) break; ++ } ++ ++ if (rqst.tc == 0x11 && rqst.cid == 0x0D && status == -EPERM) { ++ /* Base state quirk: ++ * The base state may be queried from ACPI when the EC is still ++ * suspended. In this case it will return '-EPERM'. This query ++ * will only be triggered from the ACPI lid GPE interrupt, thus ++ * we are either in laptop or studio mode (base status 0x01 or ++ * 0x02). Furthermore, we will only get here if the device (and ++ * EC) have been suspended. ++ * ++ * We now assume that the device is in laptop mode (0x01). This ++ * has the drawback that it will wake the device when unfolding ++ * it in studio mode, but it also allows us to avoid actively ++ * waiting for the EC to wake up, which may incur a notable ++ * delay. ++ */ ++ ++ buffer->status = 0x00; ++ buffer->len = 0x03; ++ buffer->data.out.status = 0x00; ++ buffer->data.out.len = 0x01; ++ buffer->data.out.pld[0] = 0x01; ++ ++ } else if (!status) { // success ++ buffer->status = 0x00; ++ buffer->len = result.len + 2; ++ buffer->data.out.status = 0x00; ++ buffer->data.out.len = result.len; ++ memcpy(&buffer->data.out.pld[0], result.data, result.len); ++ ++ } else { // failure ++ dev_err(ctx->dev, SAN_RQST_TAG "failed with error %d\n", status); ++ buffer->status = 0x00; ++ buffer->len = 0x02; ++ buffer->data.out.status = 0x01; // indicate _SSH error ++ buffer->data.out.len = 0x00; ++ } ++ ++ kfree(result.data); ++ ++ return AE_OK; ++} ++ ++static acpi_status ++san_rqsg(struct san_opreg_context *ctx, struct gsb_buffer *buffer) ++{ ++ struct gsb_data_rqsx *gsb_rqsg = san_validate_rqsx(ctx->dev, "RQSG", buffer); ++ struct surface_sam_san_rqsg rqsg = {}; ++ int status; ++ ++ if (!gsb_rqsg) { ++ return AE_OK; ++ } ++ ++ rqsg.tc = gsb_rqsg->tc; ++ rqsg.cid = gsb_rqsg->cid; ++ rqsg.iid = gsb_rqsg->iid; ++ rqsg.cdl = gsb_rqsg->cdl; ++ rqsg.pld = &gsb_rqsg->pld[0]; ++ ++ status = san_call_rqsg_handler(&rqsg); ++ if (!status) { ++ buffer->status = 0x00; ++ buffer->len = 0x02; ++ buffer->data.out.status = 0x00; ++ buffer->data.out.len = 0x00; ++ } else { ++ dev_err(ctx->dev, SAN_RQSG_TAG "failed with error %d\n", status); ++ buffer->status = 0x00; ++ buffer->len = 0x02; ++ buffer->data.out.status = 0x01; // indicate _SSH error ++ buffer->data.out.len = 0x00; ++ } ++ ++ return AE_OK; ++} ++ ++ ++static acpi_status ++san_opreg_handler(u32 function, acpi_physical_address command, ++ u32 bits, u64 *value64, ++ void *opreg_context, void *region_context) ++{ ++ struct san_opreg_context *context = opreg_context; ++ struct gsb_buffer *buffer = (struct gsb_buffer *)value64; ++ int accessor_type = (0xFFFF0000 & function) >> 16; ++ ++ if (command != 0) { ++ dev_warn(context->dev, "unsupported command: 0x%02llx\n", command); ++ return AE_OK; ++ } ++ ++ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { ++ dev_err(context->dev, "invalid access type: 0x%02x\n", accessor_type); ++ return AE_OK; ++ } ++ ++ // buffer must have at least contain the command-value ++ if (buffer->len == 0) { ++ dev_err(context->dev, "request-package too small\n"); ++ return AE_OK; ++ } ++ ++ switch (buffer->data.in.cv) { ++ case 0x01: return san_rqst(context, buffer); ++ case 0x02: return san_etwl(context, buffer); ++ case 0x03: return san_rqsg(context, buffer); ++ } ++ ++ dev_warn(context->dev, "unsupported SAN0 request (cv: 0x%02x)\n", buffer->data.in.cv); ++ return AE_OK; ++} ++ ++static int san_enable_power_events(struct platform_device *pdev) ++{ ++ int status; ++ ++ status = surface_sam_ssh_set_delayed_event_handler( ++ SAM_EVENT_PWR_RQID, san_evt_power, ++ san_evt_power_delay, &pdev->dev); ++ if (status) ++ return status; ++ ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID); ++ if (status) { ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_PWR_RQID); ++ return status; ++ } ++ ++ return 0; ++} ++ ++static int san_enable_thermal_events(struct platform_device *pdev) ++{ ++ int status; ++ ++ status = surface_sam_ssh_set_event_handler( ++ SAM_EVENT_TEMP_RQID, san_evt_thermal, ++ &pdev->dev); ++ if (status) ++ return status; ++ ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID); ++ if (status) { ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_TEMP_RQID); ++ return status; ++ } ++ ++ return 0; ++} ++ ++static void san_disable_power_events(void) ++{ ++ surface_sam_ssh_disable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID); ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_PWR_RQID); ++} ++ ++static void san_disable_thermal_events(void) ++{ ++ surface_sam_ssh_disable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID); ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_TEMP_RQID); ++} ++ ++ ++static int san_enable_events(struct platform_device *pdev) ++{ ++ struct san_drvdata *drvdata = platform_get_drvdata(pdev); ++ int status; ++ ++ status = san_enable_thermal_events(pdev); ++ if (status) ++ return status; ++ ++ /* ++ * We have to figure out if this device uses SAN or requires a separate ++ * driver for the battery. If it uses the separate driver, that driver ++ * will enable and handle power events. ++ */ ++ drvdata->has_power_events = acpi_has_method(NULL, "\\_SB.BAT1._BST"); ++ if (drvdata->has_power_events) { ++ status = san_enable_power_events(pdev); ++ if (status) ++ goto err; ++ } ++ ++ return 0; ++ ++err: ++ san_disable_thermal_events(); ++ return status; ++} ++ ++static void san_disable_events(struct platform_device *pdev) ++{ ++ struct san_drvdata *drvdata = platform_get_drvdata(pdev); ++ ++ san_disable_thermal_events(); ++ if (drvdata->has_power_events) ++ san_disable_power_events(); ++} ++ ++ ++static int san_consumers_link(struct platform_device *pdev, ++ const struct san_acpi_consumer *cons, ++ struct san_consumers *out) ++{ ++ const struct san_acpi_consumer *con; ++ struct san_consumer_link *links, *link; ++ struct acpi_device *adev; ++ acpi_handle handle; ++ u32 max_links = 0; ++ int status; ++ ++ if (!cons) { ++ return 0; ++ } ++ ++ // count links ++ for (con = cons; con->path; ++con) { ++ max_links += 1; ++ } ++ ++ // allocate ++ links = kzalloc(max_links * sizeof(struct san_consumer_link), GFP_KERNEL); ++ link = &links[0]; ++ ++ if (!links) { ++ return -ENOMEM; ++ } ++ ++ // create links ++ for (con = cons; con->path; ++con) { ++ status = acpi_get_handle(NULL, con->path, &handle); ++ if (status) { ++ if (con->required || status != AE_NOT_FOUND) { ++ status = -ENXIO; ++ goto cleanup; ++ } else { ++ continue; ++ } ++ } ++ ++ status = acpi_bus_get_device(handle, &adev); ++ if (status) { ++ goto cleanup; ++ } ++ ++ link->link = device_link_add(&adev->dev, &pdev->dev, con->flags); ++ if (!(link->link)) { ++ status = -EFAULT; ++ goto cleanup; ++ } ++ link->properties = con; ++ ++ link += 1; ++ } ++ ++ out->num = link - links; ++ out->links = links; ++ ++ return 0; ++ ++cleanup: ++ for (link = link - 1; link >= links; --link) { ++ if (link->properties->flags & DL_FLAG_STATELESS) { ++ device_link_del(link->link); ++ } ++ } ++ ++ return status; ++} ++ ++static void san_consumers_unlink(struct san_consumers *consumers) { ++ u32 i; ++ ++ if (!consumers) { ++ return; ++ } ++ ++ for (i = 0; i < consumers->num; ++i) { ++ if (consumers->links[i].properties->flags & DL_FLAG_STATELESS) { ++ device_link_del(consumers->links[i].link); ++ } ++ } ++ ++ kfree(consumers->links); ++ ++ consumers->num = 0; ++ consumers->links = NULL; ++} ++ ++static int surface_sam_san_probe(struct platform_device *pdev) ++{ ++ const struct san_acpi_consumer *cons; ++ struct san_drvdata *drvdata; ++ acpi_handle san = ACPI_HANDLE(&pdev->dev); // _SAN device node ++ int status; ++ ++ /* ++ * Defer probe if the _SSH driver has not set up the controller yet. This ++ * makes sure we do not fail any initial requests (e.g. _STA request without ++ * which the battery does not get set up correctly). Otherwise register as ++ * consumer to set up a device_link. ++ */ ++ status = surface_sam_ssh_consumer_register(&pdev->dev); ++ if (status) { ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ } ++ ++ drvdata = kzalloc(sizeof(struct san_drvdata), GFP_KERNEL); ++ if (!drvdata) { ++ return -ENOMEM; ++ } ++ ++ drvdata->opreg_ctx.dev = &pdev->dev; ++ ++ cons = acpi_device_get_match_data(&pdev->dev); ++ status = san_consumers_link(pdev, cons, &drvdata->consumers); ++ if (status) { ++ goto err_consumers; ++ } ++ ++ platform_set_drvdata(pdev, drvdata); ++ ++ status = acpi_install_address_space_handler(san, ++ ACPI_ADR_SPACE_GSBUS, ++ &san_opreg_handler, ++ NULL, &drvdata->opreg_ctx); ++ ++ if (ACPI_FAILURE(status)) { ++ status = -ENODEV; ++ goto err_install_handler; ++ } ++ ++ status = san_enable_events(pdev); ++ if (status) { ++ goto err_enable_events; ++ } ++ ++ mutex_lock(&rqsg_if.lock); ++ if (!rqsg_if.san_dev) { ++ rqsg_if.san_dev = &pdev->dev; ++ } else { ++ status = -EBUSY; ++ } ++ mutex_unlock(&rqsg_if.lock); ++ ++ if (status) { ++ goto err_install_dev; ++ } ++ ++ acpi_walk_dep_device_list(san); ++ return 0; ++ ++err_install_dev: ++ san_disable_events(pdev); ++err_enable_events: ++ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler); ++err_install_handler: ++ platform_set_drvdata(san, NULL); ++ san_consumers_unlink(&drvdata->consumers); ++err_consumers: ++ kfree(drvdata); ++ return status; ++} ++ ++static int surface_sam_san_remove(struct platform_device *pdev) ++{ ++ struct san_drvdata *drvdata = platform_get_drvdata(pdev); ++ acpi_handle san = ACPI_HANDLE(&pdev->dev); // _SAN device node ++ acpi_status status = AE_OK; ++ ++ mutex_lock(&rqsg_if.lock); ++ rqsg_if.san_dev = NULL; ++ mutex_unlock(&rqsg_if.lock); ++ ++ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler); ++ san_disable_events(pdev); ++ ++ san_consumers_unlink(&drvdata->consumers); ++ kfree(drvdata); ++ ++ platform_set_drvdata(pdev, NULL); ++ return status; ++} ++ ++ ++static const struct san_acpi_consumer san_mshw0091_consumers[] = { ++ { "\\_SB.SRTC", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, ++ { "\\ADP1", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, ++ { "\\_SB.BAT1", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, ++ { "\\_SB.BAT2", false, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, ++ { }, ++}; ++ ++static const struct acpi_device_id surface_sam_san_match[] = { ++ { "MSHW0091", (long unsigned int) san_mshw0091_consumers }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_sam_san_match); ++ ++static struct platform_driver surface_sam_san = { ++ .probe = surface_sam_san_probe, ++ .remove = surface_sam_san_remove, ++ .driver = { ++ .name = "surface_sam_san", ++ .acpi_match_table = ACPI_PTR(surface_sam_san_match), ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_sam_san); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface ACPI Notify Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_san.h b/drivers/platform/x86/surface_sam/surface_sam_san.h +new file mode 100644 +index 000000000000..1ea8713db367 +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_san.h +@@ -0,0 +1,29 @@ ++/* ++ * Interface for Surface ACPI/Notify (SAN). ++ * ++ * The SAN is the main interface between the Surface Serial Hub (SSH) and the ++ * Surface/System Aggregator Module (SAM). It allows requests to be translated ++ * from ACPI to SSH/SAM. It also interfaces with the discrete GPU hot-plug ++ * driver. ++ */ ++ ++#ifndef _SURFACE_SAM_SAN_H ++#define _SURFACE_SAM_SAN_H ++ ++#include ++ ++ ++struct surface_sam_san_rqsg { ++ u8 tc; // target category ++ u8 cid; // command ID ++ u8 iid; // instance ID ++ u8 cdl; // command data length (lenght of payload) ++ u8 *pld; // pointer to payload of length cdl ++}; ++ ++typedef int (*surface_sam_san_rqsg_handler_fn)(struct surface_sam_san_rqsg *rqsg, void *data); ++ ++int surface_sam_san_consumer_register(struct device *consumer, u32 flags); ++int surface_sam_san_set_rqsg_handler(surface_sam_san_rqsg_handler_fn fn, void *data); ++ ++#endif /* _SURFACE_SAM_SAN_H */ +diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid.c b/drivers/platform/x86/surface_sam/surface_sam_sid.c +new file mode 100644 +index 000000000000..f64dcd590494 +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_sid.c +@@ -0,0 +1,117 @@ ++/* ++ * Surface Integration Driver. ++ * MFD driver to provide device/model dependent functionality. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++static const struct mfd_cell sid_devs_sp4[] = { ++ { .name = "surface_sam_sid_gpelid", .id = -1 }, ++ { }, ++}; ++ ++static const struct mfd_cell sid_devs_sp7[] = { ++ { .name = "surface_sam_sid_gpelid", .id = -1 }, ++ { .name = "surface_sam_sid_ac", .id = -1 }, ++ { .name = "surface_sam_sid_battery", .id = -1 }, ++ { }, ++}; ++ ++static const struct mfd_cell sid_devs_sb1[] = { ++ { .name = "surface_sam_sid_gpelid", .id = -1 }, ++ { }, ++}; ++ ++static const struct mfd_cell sid_devs_sb2[] = { ++ { .name = "surface_sam_sid_gpelid", .id = -1 }, ++ { .name = "surface_sam_sid_perfmode", .id = -1 }, ++ { }, ++}; ++ ++static const struct mfd_cell sid_devs_sl1[] = { ++ { .name = "surface_sam_sid_gpelid", .id = -1 }, ++ { }, ++}; ++ ++static const struct mfd_cell sid_devs_sl2[] = { ++ { .name = "surface_sam_sid_gpelid", .id = -1 }, ++ { }, ++}; ++ ++static const struct mfd_cell sid_devs_sl3_13[] = { ++ { .name = "surface_sam_sid_gpelid", .id = -1 }, ++ { .name = "surface_sam_sid_vhf", .id = -1 }, ++ { .name = "surface_sam_sid_ac", .id = -1 }, ++ { .name = "surface_sam_sid_battery", .id = -1 }, ++ { }, ++}; ++ ++static const struct mfd_cell sid_devs_sl3_15[] = { ++ { .name = "surface_sam_sid_vhf", .id = -1 }, ++ { .name = "surface_sam_sid_ac", .id = -1 }, ++ { .name = "surface_sam_sid_battery", .id = -1 }, ++ { }, ++}; ++ ++static const struct acpi_device_id surface_sam_sid_match[] = { ++ { "MSHW0081", (unsigned long)sid_devs_sp4 }, /* Surface Pro 4, 5, and 6 */ ++ { "MSHW0116", (unsigned long)sid_devs_sp7 }, /* Surface Pro 7 */ ++ { "MSHW0080", (unsigned long)sid_devs_sb1 }, /* Surface Book 1 */ ++ { "MSHW0107", (unsigned long)sid_devs_sb2 }, /* Surface Book 2 */ ++ { "MSHW0086", (unsigned long)sid_devs_sl1 }, /* Surface Laptop 1 */ ++ { "MSHW0112", (unsigned long)sid_devs_sl2 }, /* Surface Laptop 2 */ ++ { "MSHW0114", (unsigned long)sid_devs_sl3_13 }, /* Surface Laptop 3 (13") */ ++ { "MSHW0110", (unsigned long)sid_devs_sl3_15 }, /* Surface Laptop 3 (15") */ ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_sam_sid_match); ++ ++ ++static int surface_sam_sid_probe(struct platform_device *pdev) ++{ ++ const struct acpi_device_id *match; ++ const struct mfd_cell *cells, *p; ++ ++ match = acpi_match_device(surface_sam_sid_match, &pdev->dev); ++ if (!match) ++ return -ENODEV; ++ ++ cells = (struct mfd_cell *)match->driver_data; ++ if (!cells) ++ return -ENODEV; ++ ++ for (p = cells; p->name; ++p) { ++ /* just count */ ++ } ++ ++ if (p == cells) ++ return -ENODEV; ++ ++ return mfd_add_devices(&pdev->dev, 0, cells, p - cells, NULL, 0, NULL); ++} ++ ++static int surface_sam_sid_remove(struct platform_device *pdev) ++{ ++ mfd_remove_devices(&pdev->dev); ++ return 0; ++} ++ ++static struct platform_driver surface_sam_sid = { ++ .probe = surface_sam_sid_probe, ++ .remove = surface_sam_sid_remove, ++ .driver = { ++ .name = "surface_sam_sid", ++ .acpi_match_table = ACPI_PTR(surface_sam_sid_match), ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_sam_sid); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface Integration Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c b/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c +new file mode 100644 +index 000000000000..ce32ebf4d94d +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c +@@ -0,0 +1,219 @@ ++/* ++ * Surface Lid driver to enable wakeup from suspend via the lid. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++struct sid_lid_device { ++ const char *acpi_path; ++ const u32 gpe_number; ++}; ++ ++ ++static const struct sid_lid_device lid_device_l17 = { ++ .acpi_path = "\\_SB.LID0", ++ .gpe_number = 0x17, ++}; ++ ++static const struct sid_lid_device lid_device_l4D = { ++ .acpi_path = "\\_SB.LID0", ++ .gpe_number = 0x4D, ++}; ++ ++static const struct sid_lid_device lid_device_l4F = { ++ .acpi_path = "\\_SB.LID0", ++ .gpe_number = 0x4F, ++}; ++ ++static const struct sid_lid_device lid_device_l57 = { ++ .acpi_path = "\\_SB.LID0", ++ .gpe_number = 0x57, ++}; ++ ++ ++static const struct dmi_system_id dmi_lid_device_table[] = { ++ { ++ .ident = "Surface Pro 4", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), ++ }, ++ .driver_data = (void *)&lid_device_l17, ++ }, ++ { ++ .ident = "Surface Pro 5", ++ .matches = { ++ /* match for SKU here due to generic product name "Surface Pro" */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), ++ }, ++ .driver_data = (void *)&lid_device_l4F, ++ }, ++ { ++ .ident = "Surface Pro 5 (LTE)", ++ .matches = { ++ /* match for SKU here due to generic product name "Surface Pro" */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), ++ }, ++ .driver_data = (void *)&lid_device_l4F, ++ }, ++ { ++ .ident = "Surface Pro 6", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), ++ }, ++ .driver_data = (void *)&lid_device_l4F, ++ }, ++ { ++ .ident = "Surface Pro 7", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"), ++ }, ++ .driver_data = (void *)&lid_device_l4D, ++ }, ++ { ++ .ident = "Surface Book 1", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), ++ }, ++ .driver_data = (void *)&lid_device_l17, ++ }, ++ { ++ .ident = "Surface Book 2", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), ++ }, ++ .driver_data = (void *)&lid_device_l17, ++ }, ++ { ++ .ident = "Surface Laptop 1", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), ++ }, ++ .driver_data = (void *)&lid_device_l57, ++ }, ++ { ++ .ident = "Surface Laptop 2", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), ++ }, ++ .driver_data = (void *)&lid_device_l57, ++ }, ++ { ++ .ident = "Surface Laptop 3 (13\")", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"), ++ }, ++ .driver_data = (void *)&lid_device_l4D, ++ }, ++ { } ++}; ++ ++ ++static int sid_lid_enable_wakeup(const struct sid_lid_device *dev, bool enable) ++{ ++ int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; ++ int status; ++ ++ status = acpi_set_gpe_wake_mask(NULL, dev->gpe_number, action); ++ if (status) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++ ++static int surface_sam_sid_gpelid_suspend(struct device *dev) ++{ ++ const struct sid_lid_device *ldev = dev_get_drvdata(dev); ++ return sid_lid_enable_wakeup(ldev, true); ++} ++ ++static int surface_sam_sid_gpelid_resume(struct device *dev) ++{ ++ const struct sid_lid_device *ldev = dev_get_drvdata(dev); ++ return sid_lid_enable_wakeup(ldev, false); ++} ++ ++static SIMPLE_DEV_PM_OPS(surface_sam_sid_gpelid_pm, ++ surface_sam_sid_gpelid_suspend, ++ surface_sam_sid_gpelid_resume); ++ ++ ++static int surface_sam_sid_gpelid_probe(struct platform_device *pdev) ++{ ++ const struct dmi_system_id *match; ++ struct sid_lid_device *dev; ++ acpi_handle lid_handle; ++ int status; ++ ++ match = dmi_first_match(dmi_lid_device_table); ++ if (!match) ++ return -ENODEV; ++ ++ dev = match->driver_data; ++ if (!dev) ++ return -ENODEV; ++ ++ status = acpi_get_handle(NULL, (acpi_string)dev->acpi_path, &lid_handle); ++ if (status) ++ return -EFAULT; ++ ++ status = acpi_setup_gpe_for_wake(lid_handle, NULL, dev->gpe_number); ++ if (status) ++ return -EFAULT; ++ ++ status = acpi_enable_gpe(NULL, dev->gpe_number); ++ if (status) ++ return -EFAULT; ++ ++ status = sid_lid_enable_wakeup(dev, false); ++ if (status) { ++ acpi_disable_gpe(NULL, dev->gpe_number); ++ return status; ++ } ++ ++ platform_set_drvdata(pdev, dev); ++ return 0; ++} ++ ++static int surface_sam_sid_gpelid_remove(struct platform_device *pdev) ++{ ++ struct sid_lid_device *dev = platform_get_drvdata(pdev); ++ ++ /* restore default behavior without this module */ ++ sid_lid_enable_wakeup(dev, false); ++ acpi_disable_gpe(NULL, dev->gpe_number); ++ ++ platform_set_drvdata(pdev, NULL); ++ return 0; ++} ++ ++static struct platform_driver surface_sam_sid_gpelid = { ++ .probe = surface_sam_sid_gpelid_probe, ++ .remove = surface_sam_sid_gpelid_remove, ++ .driver = { ++ .name = "surface_sam_sid_gpelid", ++ .pm = &surface_sam_sid_gpelid_pm, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_sam_sid_gpelid); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface Lid Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:surface_sam_sid_gpelid"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c b/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c +new file mode 100644 +index 000000000000..880a2567cf1b +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c +@@ -0,0 +1,225 @@ ++/* ++ * Surface Performance Mode Driver. ++ * Allows to change cooling capabilities based on user preference. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "surface_sam_ssh.h" ++ ++ ++#define SID_PARAM_PERM (S_IRUGO | S_IWUSR) ++ ++enum sam_perf_mode { ++ SAM_PERF_MODE_NORMAL = 1, ++ SAM_PERF_MODE_BATTERY = 2, ++ SAM_PERF_MODE_PERF1 = 3, ++ SAM_PERF_MODE_PERF2 = 4, ++ ++ __SAM_PERF_MODE__START = 1, ++ __SAM_PERF_MODE__END = 4, ++}; ++ ++enum sid_param_perf_mode { ++ SID_PARAM_PERF_MODE_AS_IS = 0, ++ SID_PARAM_PERF_MODE_NORMAL = SAM_PERF_MODE_NORMAL, ++ SID_PARAM_PERF_MODE_BATTERY = SAM_PERF_MODE_BATTERY, ++ SID_PARAM_PERF_MODE_PERF1 = SAM_PERF_MODE_PERF1, ++ SID_PARAM_PERF_MODE_PERF2 = SAM_PERF_MODE_PERF2, ++ ++ __SID_PARAM_PERF_MODE__START = 0, ++ __SID_PARAM_PERF_MODE__END = 4, ++}; ++ ++ ++static int surface_sam_perf_mode_get(void) ++{ ++ u8 result_buf[8] = { 0 }; ++ int status; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = 0x03, ++ .cid = 0x02, ++ .iid = 0x00, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 0x01, ++ .cdl = 0x00, ++ .pld = NULL, ++ }; ++ ++ struct surface_sam_ssh_buf result = { ++ .cap = ARRAY_SIZE(result_buf), ++ .len = 0, ++ .data = result_buf, ++ }; ++ ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ if (status) { ++ return status; ++ } ++ ++ if (result.len != 8) { ++ return -EFAULT; ++ } ++ ++ return get_unaligned_le32(&result.data[0]); ++} ++ ++static int surface_sam_perf_mode_set(int perf_mode) ++{ ++ u8 payload[4] = { 0 }; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = 0x03, ++ .cid = 0x03, ++ .iid = 0x00, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 0x00, ++ .cdl = ARRAY_SIZE(payload), ++ .pld = payload, ++ }; ++ ++ if (perf_mode < __SAM_PERF_MODE__START || perf_mode > __SAM_PERF_MODE__END) { ++ return -EINVAL; ++ } ++ ++ put_unaligned_le32(perf_mode, &rqst.pld[0]); ++ return surface_sam_ssh_rqst(&rqst, NULL); ++} ++ ++ ++static int param_perf_mode_set(const char *val, const struct kernel_param *kp) ++{ ++ int perf_mode; ++ int status; ++ ++ status = kstrtoint(val, 0, &perf_mode); ++ if (status) { ++ return status; ++ } ++ ++ if (perf_mode < __SID_PARAM_PERF_MODE__START || perf_mode > __SID_PARAM_PERF_MODE__END) { ++ return -EINVAL; ++ } ++ ++ return param_set_int(val, kp); ++} ++ ++static const struct kernel_param_ops param_perf_mode_ops = { ++ .set = param_perf_mode_set, ++ .get = param_get_int, ++}; ++ ++static int param_perf_mode_init = SID_PARAM_PERF_MODE_AS_IS; ++static int param_perf_mode_exit = SID_PARAM_PERF_MODE_AS_IS; ++ ++module_param_cb(perf_mode_init, ¶m_perf_mode_ops, ¶m_perf_mode_init, SID_PARAM_PERM); ++module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_perf_mode_exit, SID_PARAM_PERM); ++ ++MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization"); ++MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit"); ++ ++ ++static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data) ++{ ++ int perf_mode; ++ ++ perf_mode = surface_sam_perf_mode_get(); ++ if (perf_mode < 0) { ++ dev_err(dev, "failed to get current performance mode: %d", perf_mode); ++ return -EIO; ++ } ++ ++ return sprintf(data, "%d\n", perf_mode); ++} ++ ++static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, ++ const char *data, size_t count) ++{ ++ int perf_mode; ++ int status; ++ ++ status = kstrtoint(data, 0, &perf_mode); ++ if (status) { ++ return status; ++ } ++ ++ status = surface_sam_perf_mode_set(perf_mode); ++ if (status) { ++ return status; ++ } ++ ++ // TODO: Should we notify ACPI here? ++ // ++ // There is a _DSM call described as ++ // WSID._DSM: Notify DPTF on Slider State change ++ // which calls ++ // ODV3 = ToInteger (Arg3) ++ // Notify(IETM, 0x88) ++ // IETM is an INT3400 Intel Dynamic Power Performance Management ++ // device, part of the DPTF framework. From the corresponding ++ // kernel driver, it looks like event 0x88 is being ignored. Also ++ // it is currently unknown what the consequecnes of setting ODV3 ++ // are. ++ ++ return count; ++} ++ ++const static DEVICE_ATTR_RW(perf_mode); ++ ++ ++static int surface_sam_sid_perfmode_probe(struct platform_device *pdev) ++{ ++ int status; ++ ++ // link to ec ++ status = surface_sam_ssh_consumer_register(&pdev->dev); ++ if (status) { ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ } ++ ++ // set initial perf_mode ++ if (param_perf_mode_init != SID_PARAM_PERF_MODE_AS_IS) { ++ status = surface_sam_perf_mode_set(param_perf_mode_init); ++ if (status) { ++ return status; ++ } ++ } ++ ++ // register perf_mode attribute ++ status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr); ++ if (status) { ++ goto err_sysfs; ++ } ++ ++ return 0; ++ ++err_sysfs: ++ surface_sam_perf_mode_set(param_perf_mode_exit); ++ return status; ++} ++ ++static int surface_sam_sid_perfmode_remove(struct platform_device *pdev) ++{ ++ sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr); ++ surface_sam_perf_mode_set(param_perf_mode_exit); ++ return 0; ++} ++ ++static struct platform_driver surface_sam_sid_perfmode = { ++ .probe = surface_sam_sid_perfmode_probe, ++ .remove = surface_sam_sid_perfmode_remove, ++ .driver = { ++ .name = "surface_sam_sid_perfmode", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_sam_sid_perfmode); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface Performance Mode Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:surface_sam_sid_perfmode"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_power.c b/drivers/platform/x86/surface_sam/surface_sam_sid_power.c +new file mode 100644 +index 000000000000..1f2c88eda394 +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_sid_power.c +@@ -0,0 +1,1259 @@ ++/* ++ * Surface SID Battery/AC Driver. ++ * Provides support for the battery and AC on 7th generation Surface devices. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "surface_sam_ssh.h" ++ ++#define SPWR_WARN KERN_WARNING KBUILD_MODNAME ": " ++#define SPWR_DEBUG KERN_DEBUG KBUILD_MODNAME ": " ++ ++ ++// TODO: check BIX/BST for unknown/unsupported 0xffffffff entries ++// TODO: DPTF (/SAN notifications)? ++// TODO: other properties? ++ ++ ++static unsigned int cache_time = 1000; ++module_param(cache_time, uint, 0644); ++MODULE_PARM_DESC(cache_time, "battery state chaching time in milliseconds [default: 1000]"); ++ ++#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) ++ ++ ++/* ++ * SAM Interface. ++ */ ++ ++#define SAM_PWR_TC 0x02 ++#define SAM_PWR_RQID 0x0002 ++ ++#define SAM_RQST_PWR_CID_STA 0x01 ++#define SAM_RQST_PWR_CID_BIX 0x02 ++#define SAM_RQST_PWR_CID_BST 0x03 ++#define SAM_RQST_PWR_CID_BTP 0x04 ++ ++#define SAM_RQST_PWR_CID_PMAX 0x0b ++#define SAM_RQST_PWR_CID_PSOC 0x0c ++#define SAM_RQST_PWR_CID_PSRC 0x0d ++#define SAM_RQST_PWR_CID_CHGI 0x0e ++#define SAM_RQST_PWR_CID_ARTG 0x0f ++ ++#define SAM_EVENT_PWR_CID_BIX 0x15 ++#define SAM_EVENT_PWR_CID_BST 0x16 ++#define SAM_EVENT_PWR_CID_ADAPTER 0x17 ++#define SAM_EVENT_PWR_CID_DPTF 0x4f ++ ++#define SAM_BATTERY_STA_OK 0x0f ++#define SAM_BATTERY_STA_PRESENT 0x10 ++ ++#define SAM_BATTERY_STATE_DISCHARGING 0x01 ++#define SAM_BATTERY_STATE_CHARGING 0x02 ++#define SAM_BATTERY_STATE_CRITICAL 0x04 ++ ++#define SAM_BATTERY_POWER_UNIT_MA 1 ++ ++ ++/* Equivalent to data returned in ACPI _BIX method */ ++struct spwr_bix { ++ u8 revision; ++ u32 power_unit; ++ u32 design_cap; ++ u32 last_full_charge_cap; ++ u32 technology; ++ u32 design_voltage; ++ u32 design_cap_warn; ++ u32 design_cap_low; ++ u32 cycle_count; ++ u32 measurement_accuracy; ++ u32 max_sampling_time; ++ u32 min_sampling_time; ++ u32 max_avg_interval; ++ u32 min_avg_interval; ++ u32 bat_cap_granularity_1; ++ u32 bat_cap_granularity_2; ++ u8 model[21]; ++ u8 serial[11]; ++ u8 type[5]; ++ u8 oem_info[21]; ++} __packed; ++ ++/* Equivalent to data returned in ACPI _BST method */ ++struct spwr_bst { ++ u32 state; ++ u32 present_rate; ++ u32 remaining_cap; ++ u32 present_voltage; ++} __packed; ++ ++/* DPTF event payload */ ++struct spwr_event_dptf { ++ u32 pmax; ++ u32 _1; /* currently unknown */ ++ u32 _2; /* currently unknown */ ++} __packed; ++ ++ ++/* Get battery status (_STA) */ ++static int sam_psy_get_sta(u8 iid, u32 *sta) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ struct surface_sam_ssh_buf result; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_STA; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x01; ++ rqst.cdl = 0x00; ++ rqst.pld = NULL; ++ ++ result.cap = sizeof(u32); ++ result.len = 0; ++ result.data = (u8 *)sta; ++ ++ return surface_sam_ssh_rqst(&rqst, &result); ++} ++ ++/* Get battery static information (_BIX) */ ++static int sam_psy_get_bix(u8 iid, struct spwr_bix *bix) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ struct surface_sam_ssh_buf result; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_BIX; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x01; ++ rqst.cdl = 0x00; ++ rqst.pld = NULL; ++ ++ result.cap = sizeof(struct spwr_bix); ++ result.len = 0; ++ result.data = (u8 *)bix; ++ ++ return surface_sam_ssh_rqst(&rqst, &result); ++} ++ ++/* Get battery dynamic information (_BST) */ ++static int sam_psy_get_bst(u8 iid, struct spwr_bst *bst) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ struct surface_sam_ssh_buf result; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_BST; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x01; ++ rqst.cdl = 0x00; ++ rqst.pld = NULL; ++ ++ result.cap = sizeof(struct spwr_bst); ++ result.len = 0; ++ result.data = (u8 *)bst; ++ ++ return surface_sam_ssh_rqst(&rqst, &result); ++} ++ ++/* Set battery trip point (_BTP) */ ++static int sam_psy_set_btp(u8 iid, u32 btp) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_BTP; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x00; ++ rqst.cdl = sizeof(u32); ++ rqst.pld = (u8 *)&btp; ++ ++ return surface_sam_ssh_rqst(&rqst, NULL); ++} ++ ++/* Get platform power soruce for battery (DPTF PSRC) */ ++static int sam_psy_get_psrc(u8 iid, u32 *psrc) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ struct surface_sam_ssh_buf result; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_PSRC; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x01; ++ rqst.cdl = 0x00; ++ rqst.pld = NULL; ++ ++ result.cap = sizeof(u32); ++ result.len = 0; ++ result.data = (u8 *)psrc; ++ ++ return surface_sam_ssh_rqst(&rqst, &result); ++} ++ ++/* Get maximum platform power for battery (DPTF PMAX) */ ++__always_unused ++static int sam_psy_get_pmax(u8 iid, u32 *pmax) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ struct surface_sam_ssh_buf result; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_PMAX; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x01; ++ rqst.cdl = 0x00; ++ rqst.pld = NULL; ++ ++ result.cap = sizeof(u32); ++ result.len = 0; ++ result.data = (u8 *)pmax; ++ ++ return surface_sam_ssh_rqst(&rqst, &result); ++} ++ ++/* Get adapter rating (DPTF ARTG) */ ++__always_unused ++static int sam_psy_get_artg(u8 iid, u32 *artg) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ struct surface_sam_ssh_buf result; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_ARTG; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x01; ++ rqst.cdl = 0x00; ++ rqst.pld = NULL; ++ ++ result.cap = sizeof(u32); ++ result.len = 0; ++ result.data = (u8 *)artg; ++ ++ return surface_sam_ssh_rqst(&rqst, &result); ++} ++ ++/* Unknown (DPTF PSOC) */ ++__always_unused ++static int sam_psy_get_psoc(u8 iid, u32 *psoc) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ struct surface_sam_ssh_buf result; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_PSOC; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x01; ++ rqst.cdl = 0x00; ++ rqst.pld = NULL; ++ ++ result.cap = sizeof(u32); ++ result.len = 0; ++ result.data = (u8 *)psoc; ++ ++ return surface_sam_ssh_rqst(&rqst, &result); ++} ++ ++/* Unknown (DPTF CHGI/ INT3403 SPPC) */ ++__always_unused ++static int sam_psy_set_chgi(u8 iid, u32 chgi) ++{ ++ struct surface_sam_ssh_rqst rqst; ++ ++ rqst.tc = SAM_PWR_TC; ++ rqst.cid = SAM_RQST_PWR_CID_CHGI; ++ rqst.iid = iid; ++ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; ++ rqst.snc = 0x00; ++ rqst.cdl = sizeof(u32); ++ rqst.pld = (u8 *)&chgi; ++ ++ return surface_sam_ssh_rqst(&rqst, NULL); ++} ++ ++ ++/* ++ * Common Power-Subsystem Interface. ++ */ ++ ++enum spwr_battery_id { ++ SPWR_BAT1, ++ SPWR_BAT2, ++ __SPWR_NUM_BAT, ++}; ++#define SPWR_BAT_SINGLE PLATFORM_DEVID_NONE ++ ++struct spwr_battery_device { ++ struct platform_device *pdev; ++ enum spwr_battery_id id; ++ ++ char name[32]; ++ struct power_supply *psy; ++ struct power_supply_desc psy_desc; ++ ++ struct delayed_work update_work; ++ ++ struct mutex lock; ++ unsigned long timestamp; ++ ++ u32 sta; ++ struct spwr_bix bix; ++ struct spwr_bst bst; ++ u32 alarm; ++}; ++ ++struct spwr_ac_device { ++ struct platform_device *pdev; ++ ++ char name[32]; ++ struct power_supply *psy; ++ struct power_supply_desc psy_desc; ++ ++ struct mutex lock; ++ ++ u32 state; ++}; ++ ++struct spwr_subsystem { ++ struct mutex lock; ++ ++ unsigned refcount; ++ struct spwr_ac_device *ac; ++ struct spwr_battery_device *battery[__SPWR_NUM_BAT]; ++}; ++ ++static struct spwr_subsystem spwr_subsystem = { ++ .lock = __MUTEX_INITIALIZER(spwr_subsystem.lock), ++}; ++ ++static enum power_supply_property spwr_ac_props[] = { ++ POWER_SUPPLY_PROP_ONLINE, ++}; ++ ++static enum power_supply_property spwr_battery_props_chg[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_CYCLE_COUNT, ++ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_CURRENT_NOW, ++ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ POWER_SUPPLY_PROP_CHARGE_NOW, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_MODEL_NAME, ++ POWER_SUPPLY_PROP_MANUFACTURER, ++ POWER_SUPPLY_PROP_SERIAL_NUMBER, ++}; ++ ++static enum power_supply_property spwr_battery_props_eng[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_CYCLE_COUNT, ++ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_POWER_NOW, ++ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, ++ POWER_SUPPLY_PROP_ENERGY_FULL, ++ POWER_SUPPLY_PROP_ENERGY_NOW, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_MODEL_NAME, ++ POWER_SUPPLY_PROP_MANUFACTURER, ++ POWER_SUPPLY_PROP_SERIAL_NUMBER, ++}; ++ ++ ++static int spwr_battery_register(struct spwr_battery_device *bat, struct platform_device *pdev, ++ enum spwr_battery_id id); ++ ++static int spwr_battery_unregister(struct spwr_battery_device *bat); ++ ++ ++inline static bool spwr_battery_present(struct spwr_battery_device *bat) ++{ ++ return bat->sta & SAM_BATTERY_STA_PRESENT; ++} ++ ++ ++inline static int spwr_battery_load_sta(struct spwr_battery_device *bat) ++{ ++ return sam_psy_get_sta(bat->id + 1, &bat->sta); ++} ++ ++inline static int spwr_battery_load_bix(struct spwr_battery_device *bat) ++{ ++ if (!spwr_battery_present(bat)) ++ return 0; ++ ++ return sam_psy_get_bix(bat->id + 1, &bat->bix); ++} ++ ++inline static int spwr_battery_load_bst(struct spwr_battery_device *bat) ++{ ++ if (!spwr_battery_present(bat)) ++ return 0; ++ ++ return sam_psy_get_bst(bat->id + 1, &bat->bst); ++} ++ ++ ++inline static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) ++{ ++ bat->alarm = value; ++ return sam_psy_set_btp(bat->id + 1, bat->alarm); ++} ++ ++inline static int spwr_battery_set_alarm(struct spwr_battery_device *bat, u32 value) ++{ ++ int status; ++ ++ mutex_lock(&bat->lock); ++ status = spwr_battery_set_alarm_unlocked(bat, value); ++ mutex_unlock(&bat->lock); ++ ++ return status; ++} ++ ++inline static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) ++{ ++ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); ++ int status; ++ ++ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) ++ return 0; ++ ++ status = spwr_battery_load_sta(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bst(bat); ++ if (status) ++ return status; ++ ++ bat->timestamp = jiffies; ++ return 0; ++} ++ ++static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) ++{ ++ int status; ++ ++ mutex_lock(&bat->lock); ++ status = spwr_battery_update_bst_unlocked(bat, cached); ++ mutex_unlock(&bat->lock); ++ ++ return status; ++} ++ ++inline static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ status = spwr_battery_load_sta(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bix(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bst(bat); ++ if (status) ++ return status; ++ ++ bat->timestamp = jiffies; ++ return 0; ++} ++ ++static int spwr_battery_update_bix(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ mutex_lock(&bat->lock); ++ status = spwr_battery_update_bix_unlocked(bat); ++ mutex_unlock(&bat->lock); ++ ++ return status; ++} ++ ++inline static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) ++{ ++ return sam_psy_get_psrc(0x00, &ac->state); ++} ++ ++static int spwr_ac_update(struct spwr_ac_device *ac) ++{ ++ int status; ++ ++ mutex_lock(&ac->lock); ++ status = spwr_ac_update_unlocked(ac); ++ mutex_unlock(&ac->lock); ++ ++ return status; ++} ++ ++ ++static int spwr_battery_recheck(struct spwr_battery_device *bat) ++{ ++ bool present = spwr_battery_present(bat); ++ u32 unit = bat->bix.power_unit; ++ int status; ++ ++ status = spwr_battery_update_bix(bat); ++ if (status) ++ return status; ++ ++ // if battery has been attached, (re-)initialize alarm ++ if (!present && spwr_battery_present(bat)) { ++ status = spwr_battery_set_alarm(bat, bat->bix.design_cap_warn); ++ if (status) ++ return status; ++ } ++ ++ // if the unit has changed, re-add the battery ++ if (unit != bat->bix.power_unit) { ++ mutex_unlock(&spwr_subsystem.lock); ++ ++ status = spwr_battery_unregister(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_register(bat, bat->pdev, bat->id); ++ } ++ ++ return status; ++} ++ ++ ++static int spwr_handle_event_bix(struct surface_sam_ssh_event *event) ++{ ++ struct spwr_battery_device *bat; ++ enum spwr_battery_id bat_id = event->iid - 1; ++ int status = 0; ++ ++ if (bat_id < 0 || bat_id >= __SPWR_NUM_BAT) { ++ printk(SPWR_WARN "invalid BIX event iid 0x%02x\n", event->iid); ++ bat_id = SPWR_BAT1; ++ } ++ ++ mutex_lock(&spwr_subsystem.lock); ++ bat = spwr_subsystem.battery[bat_id]; ++ if (bat) { ++ status = spwr_battery_recheck(bat); ++ if (!status) ++ power_supply_changed(bat->psy); ++ } ++ ++ mutex_unlock(&spwr_subsystem.lock); ++ return status; ++} ++ ++static int spwr_handle_event_bst(struct surface_sam_ssh_event *event) ++{ ++ struct spwr_battery_device *bat; ++ enum spwr_battery_id bat_id = event->iid - 1; ++ int status = 0; ++ ++ if (bat_id < 0 || bat_id >= __SPWR_NUM_BAT) { ++ printk(SPWR_WARN "invalid BST event iid 0x%02x\n", event->iid); ++ bat_id = SPWR_BAT1; ++ } ++ ++ mutex_lock(&spwr_subsystem.lock); ++ ++ bat = spwr_subsystem.battery[bat_id]; ++ if (bat) { ++ status = spwr_battery_update_bst(bat, false); ++ if (!status) ++ power_supply_changed(bat->psy); ++ } ++ ++ mutex_unlock(&spwr_subsystem.lock); ++ return status; ++} ++ ++static int spwr_handle_event_adapter(struct surface_sam_ssh_event *event) ++{ ++ struct spwr_battery_device *bat1 = NULL; ++ struct spwr_battery_device *bat2 = NULL; ++ struct spwr_ac_device *ac; ++ int status = 0; ++ ++ mutex_lock(&spwr_subsystem.lock); ++ ++ ac = spwr_subsystem.ac; ++ if (ac) { ++ status = spwr_ac_update(ac); ++ if (status) ++ goto out; ++ ++ power_supply_changed(ac->psy); ++ } ++ ++ /* ++ * Handle battery update quirk: ++ * When the battery is fully charged and the adapter is plugged in or ++ * removed, the EC does not send a separate event for the state ++ * (charging/discharging) change. Furthermore it may take some time until ++ * the state is updated on the battery. Schedule an update to solve this. ++ */ ++ ++ bat1 = spwr_subsystem.battery[SPWR_BAT1]; ++ if (bat1 && bat1->bst.remaining_cap >= bat1->bix.last_full_charge_cap) ++ schedule_delayed_work(&bat1->update_work, SPWR_AC_BAT_UPDATE_DELAY); ++ ++ bat2 = spwr_subsystem.battery[SPWR_BAT2]; ++ if (bat2 && bat2->bst.remaining_cap >= bat2->bix.last_full_charge_cap) ++ schedule_delayed_work(&bat2->update_work, SPWR_AC_BAT_UPDATE_DELAY); ++ ++out: ++ mutex_unlock(&spwr_subsystem.lock); ++ return status; ++} ++ ++static int spwr_handle_event_dptf(struct surface_sam_ssh_event *event) ++{ ++ return 0; // TODO: spwr_handle_event_dptf ++} ++ ++static int spwr_handle_event(struct surface_sam_ssh_event *event, void *data) ++{ ++ printk(SPWR_DEBUG "power event (cid = 0x%02x)\n", event->cid); ++ ++ switch (event->cid) { ++ case SAM_EVENT_PWR_CID_BIX: ++ return spwr_handle_event_bix(event); ++ ++ case SAM_EVENT_PWR_CID_BST: ++ return spwr_handle_event_bst(event); ++ ++ case SAM_EVENT_PWR_CID_ADAPTER: ++ return spwr_handle_event_adapter(event); ++ ++ case SAM_EVENT_PWR_CID_DPTF: ++ return spwr_handle_event_dptf(event); ++ ++ default: ++ printk(SPWR_WARN "unhandled power event (cid = 0x%02x)\n", event->cid); ++ return 0; ++ } ++} ++ ++static void spwr_battery_update_bst_workfn(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct spwr_battery_device *bat = container_of(dwork, struct spwr_battery_device, update_work); ++ int status; ++ ++ status = spwr_battery_update_bst(bat, false); ++ if (!status) ++ power_supply_changed(bat->psy); ++ ++ if (status) ++ dev_err(&bat->pdev->dev, "failed to update battery state: %d\n", status); ++} ++ ++ ++inline static int spwr_battery_prop_status(struct spwr_battery_device *bat) ++{ ++ if (bat->bst.state & SAM_BATTERY_STATE_DISCHARGING) ++ return POWER_SUPPLY_STATUS_DISCHARGING; ++ ++ if (bat->bst.state & SAM_BATTERY_STATE_CHARGING) ++ return POWER_SUPPLY_STATUS_CHARGING; ++ ++ if (bat->bix.last_full_charge_cap == bat->bst.remaining_cap) ++ return POWER_SUPPLY_STATUS_FULL; ++ ++ if (bat->bst.present_rate == 0) ++ return POWER_SUPPLY_STATUS_NOT_CHARGING; ++ ++ return POWER_SUPPLY_STATUS_UNKNOWN; ++} ++ ++inline static int spwr_battery_prop_technology(struct spwr_battery_device *bat) ++{ ++ if (!strcasecmp("NiCd", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_NiCd; ++ ++ if (!strcasecmp("NiMH", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_NiMH; ++ ++ if (!strcasecmp("LION", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_LION; ++ ++ if (!strncasecmp("LI-ION", bat->bix.type, 6)) ++ return POWER_SUPPLY_TECHNOLOGY_LION; ++ ++ if (!strcasecmp("LiP", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_LIPO; ++ ++ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; ++} ++ ++inline static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) ++{ ++ if (bat->bst.remaining_cap && bat->bix.last_full_charge_cap) ++ return bat->bst.remaining_cap * 100 / bat->bix.last_full_charge_cap; ++ else ++ return 0; ++} ++ ++inline static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) ++{ ++ if (bat->bst.state & SAM_BATTERY_STATE_CRITICAL) ++ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; ++ ++ if (bat->bst.remaining_cap >= bat->bix.last_full_charge_cap) ++ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; ++ ++ if (bat->bst.remaining_cap <= bat->alarm) ++ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; ++ ++ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; ++} ++ ++static int spwr_ac_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); ++ int status; ++ ++ mutex_lock(&ac->lock); ++ ++ status = spwr_ac_update_unlocked(ac); ++ if (status) ++ goto out; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ val->intval = ac->state == 1; ++ break; ++ ++ default: ++ status = -EINVAL; ++ goto out; ++ } ++ ++out: ++ mutex_unlock(&ac->lock); ++ return status; ++} ++ ++static int spwr_battery_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ int status; ++ ++ mutex_lock(&bat->lock); ++ ++ status = spwr_battery_update_bst_unlocked(bat, true); ++ if (status) ++ goto out; ++ ++ // abort if battery is not present ++ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { ++ status = -ENODEV; ++ goto out; ++ } ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_STATUS: ++ val->intval = spwr_battery_prop_status(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_PRESENT: ++ val->intval = spwr_battery_present(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_TECHNOLOGY: ++ val->intval = spwr_battery_prop_technology(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_CYCLE_COUNT: ++ val->intval = bat->bix.cycle_count; ++ break; ++ ++ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: ++ val->intval = bat->bix.design_voltage * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ val->intval = bat->bst.present_voltage * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CURRENT_NOW: ++ case POWER_SUPPLY_PROP_POWER_NOW: ++ val->intval = bat->bst.present_rate * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ++ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: ++ val->intval = bat->bix.design_cap * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_FULL: ++ case POWER_SUPPLY_PROP_ENERGY_FULL: ++ val->intval = bat->bix.last_full_charge_cap * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_NOW: ++ case POWER_SUPPLY_PROP_ENERGY_NOW: ++ val->intval = bat->bst.remaining_cap * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CAPACITY: ++ val->intval = spwr_battery_prop_capacity(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: ++ val->intval = spwr_battery_prop_capacity_level(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_MODEL_NAME: ++ val->strval = bat->bix.model; ++ break; ++ ++ case POWER_SUPPLY_PROP_MANUFACTURER: ++ val->strval = bat->bix.oem_info; ++ break; ++ ++ case POWER_SUPPLY_PROP_SERIAL_NUMBER: ++ val->strval = bat->bix.serial; ++ break; ++ ++ default: ++ status = -EINVAL; ++ goto out; ++ } ++ ++out: ++ mutex_unlock(&bat->lock); ++ return status; ++} ++ ++ ++static ssize_t spwr_battery_alarm_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct power_supply *psy = dev_get_drvdata(dev); ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ ++ return sprintf(buf, "%d\n", bat->alarm * 1000); ++} ++ ++static ssize_t spwr_battery_alarm_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct power_supply *psy = dev_get_drvdata(dev); ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ unsigned long value; ++ int status; ++ ++ status = kstrtoul(buf, 0, &value); ++ if (status) ++ return status; ++ ++ if (!spwr_battery_present(bat)) ++ return -ENODEV; ++ ++ status = spwr_battery_set_alarm(bat, value / 1000); ++ if (status) ++ return status; ++ ++ return count; ++} ++ ++static const struct device_attribute alarm_attr = { ++ .attr = {.name = "alarm", .mode = 0644}, ++ .show = spwr_battery_alarm_show, ++ .store = spwr_battery_alarm_store, ++}; ++ ++ ++static int spwr_subsys_init_unlocked(void) ++{ ++ int status; ++ ++ status = surface_sam_ssh_set_event_handler(SAM_PWR_RQID, spwr_handle_event, NULL); ++ if (status) { ++ goto err_handler; ++ } ++ ++ status = surface_sam_ssh_enable_event_source(SAM_PWR_TC, 0x01, SAM_PWR_RQID); ++ if (status) { ++ goto err_source; ++ } ++ ++ return 0; ++ ++err_source: ++ surface_sam_ssh_remove_event_handler(SAM_PWR_RQID); ++err_handler: ++ return status; ++} ++ ++static int spwr_subsys_deinit_unlocked(void) ++{ ++ surface_sam_ssh_disable_event_source(SAM_PWR_TC, 0x01, SAM_PWR_RQID); ++ surface_sam_ssh_remove_event_handler(SAM_PWR_RQID); ++ return 0; ++} ++ ++static inline int spwr_subsys_ref_unlocked(void) ++{ ++ int status = 0; ++ ++ if (!spwr_subsystem.refcount) ++ status = spwr_subsys_init_unlocked(); ++ ++ spwr_subsystem.refcount += 1; ++ return status; ++} ++ ++static inline int spwr_subsys_unref_unlocked(void) ++{ ++ int status = 0; ++ ++ if (spwr_subsystem.refcount) ++ spwr_subsystem.refcount -= 1; ++ ++ if (!spwr_subsystem.refcount) ++ status = spwr_subsys_deinit_unlocked(); ++ ++ return status; ++} ++ ++ ++static int spwr_ac_register(struct spwr_ac_device *ac, struct platform_device *pdev) ++{ ++ struct power_supply_config psy_cfg = {}; ++ u32 sta; ++ int status; ++ ++ // make sure the device is there and functioning properly ++ status = sam_psy_get_sta(0x00, &sta); ++ if (status) ++ return status; ++ ++ if ((sta & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) ++ return -ENODEV; ++ ++ psy_cfg.drv_data = ac; ++ ++ ac->pdev = pdev; ++ mutex_init(&ac->lock); ++ ++ snprintf(ac->name, ARRAY_SIZE(ac->name), "ADP0"); ++ ++ ac->psy_desc.name = ac->name; ++ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; ++ ac->psy_desc.properties = spwr_ac_props; ++ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); ++ ac->psy_desc.get_property = spwr_ac_get_property; ++ ++ mutex_lock(&spwr_subsystem.lock); ++ if (spwr_subsystem.ac) { ++ status = -EEXIST; ++ goto err; ++ } ++ ++ status = spwr_subsys_ref_unlocked(); ++ if (status) ++ goto err; ++ ++ ac->psy = power_supply_register(&ac->pdev->dev, &ac->psy_desc, &psy_cfg); ++ if (IS_ERR(ac->psy)) { ++ status = PTR_ERR(ac->psy); ++ goto err_unref; ++ } ++ ++ spwr_subsystem.ac = ac; ++ mutex_unlock(&spwr_subsystem.lock); ++ return 0; ++ ++err_unref: ++ spwr_subsys_unref_unlocked(); ++err: ++ mutex_unlock(&spwr_subsystem.lock); ++ mutex_destroy(&ac->lock); ++ return status; ++} ++ ++static int spwr_ac_unregister(struct spwr_ac_device *ac) ++{ ++ int status; ++ ++ mutex_lock(&spwr_subsystem.lock); ++ if (spwr_subsystem.ac != ac) { ++ mutex_unlock(&spwr_subsystem.lock); ++ return -EINVAL; ++ } ++ ++ spwr_subsystem.ac = NULL; ++ power_supply_unregister(ac->psy); ++ ++ status = spwr_subsys_unref_unlocked(); ++ mutex_unlock(&spwr_subsystem.lock); ++ ++ mutex_destroy(&ac->lock); ++ return status; ++} ++ ++static int spwr_battery_register(struct spwr_battery_device *bat, struct platform_device *pdev, ++ enum spwr_battery_id id) ++{ ++ struct power_supply_config psy_cfg = {}; ++ u32 sta; ++ int status; ++ ++ if ((id < 0 || id >= __SPWR_NUM_BAT) && id != SPWR_BAT_SINGLE) ++ return -EINVAL; ++ ++ bat->pdev = pdev; ++ bat->id = id != SPWR_BAT_SINGLE ? id : SPWR_BAT1; ++ ++ // make sure the device is there and functioning properly ++ status = sam_psy_get_sta(bat->id + 1, &sta); ++ if (status) ++ return status; ++ ++ if ((sta & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) ++ return -ENODEV; ++ ++ status = spwr_battery_update_bix_unlocked(bat); ++ if (status) ++ return status; ++ ++ if (spwr_battery_present(bat)) { ++ status = spwr_battery_set_alarm_unlocked(bat, bat->bix.design_cap_warn); ++ if (status) ++ return status; ++ } ++ ++ snprintf(bat->name, ARRAY_SIZE(bat->name), "BAT%d", bat->id); ++ bat->psy_desc.name = bat->name; ++ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; ++ ++ if (bat->bix.power_unit == SAM_BATTERY_POWER_UNIT_MA) { ++ bat->psy_desc.properties = spwr_battery_props_chg; ++ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); ++ } else { ++ bat->psy_desc.properties = spwr_battery_props_eng; ++ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); ++ } ++ ++ bat->psy_desc.get_property = spwr_battery_get_property; ++ ++ mutex_init(&bat->lock); ++ psy_cfg.drv_data = bat; ++ ++ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); ++ ++ mutex_lock(&spwr_subsystem.lock); ++ if (spwr_subsystem.battery[bat->id]) { ++ status = -EEXIST; ++ goto err; ++ } ++ ++ status = spwr_subsys_ref_unlocked(); ++ if (status) ++ goto err; ++ ++ bat->psy = power_supply_register(&bat->pdev->dev, &bat->psy_desc, &psy_cfg); ++ if (IS_ERR(bat->psy)) { ++ status = PTR_ERR(bat->psy); ++ goto err_unref; ++ } ++ ++ status = device_create_file(&bat->psy->dev, &alarm_attr); ++ if (status) ++ goto err_dereg; ++ ++ spwr_subsystem.battery[bat->id] = bat; ++ mutex_unlock(&spwr_subsystem.lock); ++ return 0; ++ ++err_dereg: ++ power_supply_unregister(bat->psy); ++err_unref: ++ spwr_subsys_unref_unlocked(); ++err: ++ mutex_unlock(&spwr_subsystem.lock); ++ return status; ++} ++ ++static int spwr_battery_unregister(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ if (bat->id < 0 || bat->id >= __SPWR_NUM_BAT) ++ return -EINVAL ; ++ ++ mutex_lock(&spwr_subsystem.lock); ++ if (spwr_subsystem.battery[bat->id] != bat) { ++ mutex_unlock(&spwr_subsystem.lock); ++ return -EINVAL; ++ } ++ ++ spwr_subsystem.battery[bat->id] = NULL; ++ ++ status = spwr_subsys_unref_unlocked(); ++ mutex_unlock(&spwr_subsystem.lock); ++ ++ cancel_delayed_work_sync(&bat->update_work); ++ device_remove_file(&bat->psy->dev, &alarm_attr); ++ power_supply_unregister(bat->psy); ++ ++ mutex_destroy(&bat->lock); ++ return status; ++} ++ ++ ++/* ++ * Battery Driver. ++ */ ++ ++#ifdef CONFIG_PM_SLEEP ++static int surface_sam_sid_battery_resume(struct device *dev) ++{ ++ struct spwr_battery_device *bat = dev_get_drvdata(dev); ++ return spwr_battery_recheck(bat); ++} ++#else ++#define surface_sam_sid_battery_resume NULL ++#endif ++ ++SIMPLE_DEV_PM_OPS(surface_sam_sid_battery_pm, NULL, surface_sam_sid_battery_resume); ++ ++static int surface_sam_sid_battery_probe(struct platform_device *pdev) ++{ ++ int status; ++ struct spwr_battery_device *bat; ++ ++ // link to ec ++ status = surface_sam_ssh_consumer_register(&pdev->dev); ++ if (status) ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ ++ bat = devm_kzalloc(&pdev->dev, sizeof(struct spwr_battery_device), GFP_KERNEL); ++ if (!bat) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, bat); ++ return spwr_battery_register(bat, pdev, pdev->id); ++} ++ ++static int surface_sam_sid_battery_remove(struct platform_device *pdev) ++{ ++ struct spwr_battery_device *bat = platform_get_drvdata(pdev); ++ return spwr_battery_unregister(bat); ++} ++ ++static struct platform_driver surface_sam_sid_battery = { ++ .probe = surface_sam_sid_battery_probe, ++ .remove = surface_sam_sid_battery_remove, ++ .driver = { ++ .name = "surface_sam_sid_battery", ++ .pm = &surface_sam_sid_battery_pm, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++/* ++ * AC Driver. ++ */ ++ ++static int surface_sam_sid_ac_probe(struct platform_device *pdev) ++{ ++ int status; ++ struct spwr_ac_device *ac; ++ ++ // link to ec ++ status = surface_sam_ssh_consumer_register(&pdev->dev); ++ if (status) ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ ++ ac = devm_kzalloc(&pdev->dev, sizeof(struct spwr_ac_device), GFP_KERNEL); ++ if (!ac) ++ return -ENOMEM; ++ ++ status = spwr_ac_register(ac, pdev); ++ if (status) ++ return status; ++ ++ platform_set_drvdata(pdev, ac); ++ return 0; ++} ++ ++static int surface_sam_sid_ac_remove(struct platform_device *pdev) ++{ ++ struct spwr_ac_device *ac = platform_get_drvdata(pdev); ++ return spwr_ac_unregister(ac); ++} ++ ++static struct platform_driver surface_sam_sid_ac = { ++ .probe = surface_sam_sid_ac_probe, ++ .remove = surface_sam_sid_ac_remove, ++ .driver = { ++ .name = "surface_sam_sid_ac", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++static int __init surface_sam_sid_power_init(void) ++{ ++ int status; ++ ++ status = platform_driver_register(&surface_sam_sid_battery); ++ if (status) ++ return status; ++ ++ status = platform_driver_register(&surface_sam_sid_ac); ++ if (status) { ++ platform_driver_unregister(&surface_sam_sid_battery); ++ return status; ++ } ++ ++ return 0; ++} ++ ++static void __exit surface_sam_sid_power_exit(void) ++{ ++ platform_driver_unregister(&surface_sam_sid_battery); ++ platform_driver_unregister(&surface_sam_sid_ac); ++} ++ ++module_init(surface_sam_sid_power_init); ++module_exit(surface_sam_sid_power_exit); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface Battery/AC Driver for 7th Generation Surface Devices"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:surface_sam_sid_ac"); ++MODULE_ALIAS("platform:surface_sam_sid_battery"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c b/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c +new file mode 100644 +index 000000000000..dc5be3a14a8c +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c +@@ -0,0 +1,440 @@ ++/* ++ * Microsofs Surface HID (VHF) driver for HID input events via SAM. ++ * Used for keyboard input events on the 7th generation Surface Laptops. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "surface_sam_ssh.h" ++ ++#define SID_VHF_INPUT_NAME "Microsoft Surface HID" ++ ++/* ++ * Request ID for VHF events. This value is based on the output of the Surface ++ * EC and should not be changed. ++ */ ++#define SAM_EVENT_SID_VHF_RQID 0x0015 ++#define SAM_EVENT_SID_VHF_TC 0x15 ++ ++#define VHF_HID_STARTED 0 ++ ++struct sid_vhf_evtctx { ++ struct device *dev; ++ struct hid_device *hid; ++ unsigned long flags; ++}; ++ ++struct sid_vhf_drvdata { ++ struct sid_vhf_evtctx event_ctx; ++}; ++ ++ ++static int sid_vhf_hid_start(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ return 0; ++} ++ ++static void sid_vhf_hid_stop(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++} ++ ++static int sid_vhf_hid_open(struct hid_device *hid) ++{ ++ struct sid_vhf_drvdata *drvdata = platform_get_drvdata(to_platform_device(hid->dev.parent)); ++ ++ hid_dbg(hid, "%s\n", __func__); ++ ++ set_bit(VHF_HID_STARTED, &drvdata->event_ctx.flags); ++ return 0; ++} ++ ++static void sid_vhf_hid_close(struct hid_device *hid) ++{ ++ ++ struct sid_vhf_drvdata *drvdata = platform_get_drvdata(to_platform_device(hid->dev.parent)); ++ ++ hid_dbg(hid, "%s\n", __func__); ++ ++ clear_bit(VHF_HID_STARTED, &drvdata->event_ctx.flags); ++} ++ ++struct surface_sam_sid_vhf_meta_rqst { ++ u8 id; ++ u32 offset; ++ u32 length; // buffer limit on send, length of data received on receive ++ u8 end; // 0x01 if end was reached ++} __packed; ++ ++struct vhf_device_metadata_info { ++ u8 len; ++ u8 _2; ++ u8 _3; ++ u8 _4; ++ u8 _5; ++ u8 _6; ++ u8 _7; ++ u16 hid_len; // hid descriptor length ++} __packed; ++ ++struct vhf_device_metadata { ++ u32 len; ++ u16 vendor_id; ++ u16 product_id; ++ u8 _1[24]; ++} __packed; ++ ++union vhf_buffer_data { ++ struct vhf_device_metadata_info info; ++ u8 pld[0x76]; ++ struct vhf_device_metadata meta; ++}; ++ ++struct surface_sam_sid_vhf_meta_resp { ++ struct surface_sam_sid_vhf_meta_rqst rqst; ++ union vhf_buffer_data data; ++} __packed; ++ ++ ++static int vhf_get_metadata(u8 iid, struct vhf_device_metadata *meta) ++{ ++ int status; ++ ++ struct surface_sam_sid_vhf_meta_resp resp = { ++ .rqst = { ++ .id = 2, ++ .offset = 0, ++ .length = 0x76, ++ .end = 0 ++ } ++ }; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = 0x15, ++ .cid = 0x04, ++ .iid = iid, ++ .pri = 0x02, ++ .snc = 0x01, ++ .cdl = sizeof(struct surface_sam_sid_vhf_meta_rqst), ++ .pld = (u8*)&resp.rqst, ++ }; ++ ++ struct surface_sam_ssh_buf result = { ++ .cap = sizeof(struct surface_sam_sid_vhf_meta_resp), ++ .len = 0, ++ .data = (u8*)&resp, ++ }; ++ ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ if (status) { ++ return status; ++ } ++ ++ *meta = resp.data.meta; ++ ++ return 0; ++} ++ ++static int vhf_get_hid_descriptor(struct hid_device *hid, u8 iid, u8 **desc, int *size) ++{ ++ int status, len; ++ u8 *buf; ++ ++ struct surface_sam_sid_vhf_meta_resp resp = { ++ .rqst = { ++ .id = 0, ++ .offset = 0, ++ .length = 0x76, ++ .end = 0, ++ } ++ }; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = 0x15, ++ .cid = 0x04, ++ .iid = iid, ++ .pri = 0x02, ++ .snc = 0x01, ++ .cdl = sizeof(struct surface_sam_sid_vhf_meta_rqst), ++ .pld = (u8*)&resp.rqst, ++ }; ++ ++ struct surface_sam_ssh_buf result = { ++ .cap = sizeof(struct surface_sam_sid_vhf_meta_resp), ++ .len = 0, ++ .data = (u8*)&resp, ++ }; ++ ++ // first fetch 00 to get the total length ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ if (status) { ++ return status; ++ } ++ ++ len = resp.data.info.hid_len; ++ ++ // allocate a buffer for the descriptor ++ buf = kzalloc(len, GFP_KERNEL); ++ ++ // then, iterate and write into buffer, copying out bytes ++ resp.rqst.id = 1; ++ resp.rqst.offset = 0; ++ resp.rqst.length = 0x76; ++ resp.rqst.end = 0; ++ ++ while (!resp.rqst.end && resp.rqst.offset < len) { ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ if (status) { ++ kfree(buf); ++ return status; ++ } ++ memcpy(buf + resp.rqst.offset, resp.data.pld, resp.rqst.length); ++ ++ resp.rqst.offset += resp.rqst.length; ++ } ++ ++ *desc = buf; ++ *size = len; ++ ++ return 0; ++} ++ ++static int sid_vhf_hid_parse(struct hid_device *hid) ++{ ++ int ret = 0, size; ++ u8 *buf; ++ ++ ret = vhf_get_hid_descriptor(hid, 0x00, &buf, &size); ++ if (ret != 0) { ++ hid_err(hid, "Failed to read HID descriptor from device: %d\n", ret); ++ return -EIO; ++ } ++ hid_dbg(hid, "HID descriptor of device:"); ++ print_hex_dump_debug("descriptor:", DUMP_PREFIX_OFFSET, 16, 1, buf, size, false); ++ ++ ret = hid_parse_report(hid, buf, size); ++ kfree(buf); ++ return ret; ++ ++} ++ ++static int sid_vhf_hid_raw_request(struct hid_device *hid, unsigned char ++ reportnum, u8 *buf, size_t len, unsigned char rtype, int ++ reqtype) ++{ ++ int status; ++ u8 cid; ++ struct surface_sam_ssh_rqst rqst = {}; ++ struct surface_sam_ssh_buf result = {}; ++ ++ hid_dbg(hid, "%s: reportnum=%#04x rtype=%i reqtype=%i\n", __func__, reportnum, rtype, reqtype); ++ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); ++ ++ // Byte 0 is the report number. Report data starts at byte 1. ++ buf[0] = reportnum; ++ ++ switch (rtype) { ++ case HID_OUTPUT_REPORT: ++ cid = 0x01; ++ break; ++ case HID_FEATURE_REPORT: ++ switch (reqtype) { ++ case HID_REQ_GET_REPORT: ++ // The EC doesn't respond to GET FEATURE for these touchpad reports ++ // we immediately discard to avoid waiting for a timeout. ++ if (reportnum == 6 || reportnum == 7 || reportnum == 8 || reportnum == 9 || reportnum == 0x0b) { ++ hid_dbg(hid, "%s: skipping get feature report for 0x%02x\n", __func__, reportnum); ++ return 0; ++ } ++ ++ cid = 0x02; ++ break; ++ case HID_REQ_SET_REPORT: ++ cid = 0x03; ++ break; ++ default: ++ hid_err(hid, "%s: unknown req type 0x%02x\n", __func__, rtype); ++ return -EIO; ++ } ++ break; ++ default: ++ hid_err(hid, "%s: unknown report type 0x%02x\n", __func__, reportnum); ++ return -EIO; ++ } ++ ++ rqst.tc = SAM_EVENT_SID_VHF_TC; ++ rqst.pri = SURFACE_SAM_PRIORITY_HIGH; ++ rqst.iid = 0x00; // windows tends to distinguish iids, but EC will take it ++ rqst.cid = cid; ++ rqst.snc = HID_REQ_GET_REPORT == reqtype ? 0x01 : 0x00; ++ rqst.cdl = HID_REQ_GET_REPORT == reqtype ? 0x01 : len; ++ rqst.pld = buf; ++ ++ result.cap = len; ++ result.len = 0; ++ result.data = buf; ++ ++ hid_dbg(hid, "%s: sending to cid=%#04x snc=%#04x\n", __func__, cid, HID_REQ_GET_REPORT == reqtype); ++ ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ hid_dbg(hid, "%s: status %i\n", __func__, status); ++ ++ if (status) { ++ return status; ++ } ++ ++ if (result.len > 0) { ++ print_hex_dump_debug("response:", DUMP_PREFIX_OFFSET, 16, 1, result.data, result.len, false); ++ } ++ ++ return result.len; ++} ++ ++static struct hid_ll_driver sid_vhf_hid_ll_driver = { ++ .start = sid_vhf_hid_start, ++ .stop = sid_vhf_hid_stop, ++ .open = sid_vhf_hid_open, ++ .close = sid_vhf_hid_close, ++ .parse = sid_vhf_hid_parse, ++ .raw_request = sid_vhf_hid_raw_request, ++}; ++ ++ ++static struct hid_device *sid_vhf_create_hid_device(struct platform_device *pdev, struct vhf_device_metadata *meta) ++{ ++ struct hid_device *hid; ++ ++ hid = hid_allocate_device(); ++ if (IS_ERR(hid)) { ++ return hid; ++ } ++ ++ hid->dev.parent = &pdev->dev; ++ ++ hid->bus = BUS_VIRTUAL; ++ hid->vendor = meta->vendor_id; ++ hid->product = meta->product_id; ++ ++ hid->ll_driver = &sid_vhf_hid_ll_driver; ++ ++ sprintf(hid->name, "%s", SID_VHF_INPUT_NAME); ++ ++ return hid; ++} ++ ++static int sid_vhf_event_handler(struct surface_sam_ssh_event *event, void *data) ++{ ++ struct sid_vhf_evtctx *ctx = (struct sid_vhf_evtctx *)data; ++ ++ // skip if HID hasn't started yet ++ if (!test_bit(VHF_HID_STARTED, &ctx->flags)) { ++ return 0; ++ } ++ ++ if (event->tc == SAM_EVENT_SID_VHF_TC && (event->cid == 0x00 || event->cid == 0x03 || event->cid == 0x04)) { ++ return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1); ++ } ++ ++ dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid); ++ return 0; ++} ++ ++static int surface_sam_sid_vhf_probe(struct platform_device *pdev) ++{ ++ struct sid_vhf_drvdata *drvdata; ++ struct vhf_device_metadata meta = {}; ++ struct hid_device *hid; ++ int status; ++ ++ // add device link to EC ++ status = surface_sam_ssh_consumer_register(&pdev->dev); ++ if (status) { ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ } ++ ++ drvdata = kzalloc(sizeof(struct sid_vhf_drvdata), GFP_KERNEL); ++ if (!drvdata) { ++ return -ENOMEM; ++ } ++ ++ status = vhf_get_metadata(0x00, &meta); ++ if (status) { ++ goto err_create_hid; ++ } ++ ++ hid = sid_vhf_create_hid_device(pdev, &meta); ++ if (IS_ERR(hid)) { ++ status = PTR_ERR(hid); ++ goto err_create_hid; ++ } ++ ++ drvdata->event_ctx.dev = &pdev->dev; ++ drvdata->event_ctx.hid = hid; ++ ++ platform_set_drvdata(pdev, drvdata); ++ ++ status = surface_sam_ssh_set_event_handler( ++ SAM_EVENT_SID_VHF_RQID, ++ sid_vhf_event_handler, ++ &drvdata->event_ctx); ++ if (status) { ++ goto err_event_handler; ++ } ++ ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_SID_VHF_TC, 0x01, SAM_EVENT_SID_VHF_RQID); ++ if (status) { ++ goto err_event_source; ++ } ++ ++ status = hid_add_device(hid); ++ if (status) { ++ goto err_add_hid; ++ } ++ ++ return 0; ++ ++err_add_hid: ++ surface_sam_ssh_disable_event_source(SAM_EVENT_SID_VHF_TC, 0x01, SAM_EVENT_SID_VHF_RQID); ++err_event_source: ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_SID_VHF_RQID); ++err_event_handler: ++ hid_destroy_device(hid); ++ platform_set_drvdata(pdev, NULL); ++err_create_hid: ++ kfree(drvdata); ++ return status; ++} ++ ++static int surface_sam_sid_vhf_remove(struct platform_device *pdev) ++{ ++ struct sid_vhf_drvdata *drvdata = platform_get_drvdata(pdev); ++ ++ surface_sam_ssh_disable_event_source(SAM_EVENT_SID_VHF_TC, 0x01, SAM_EVENT_SID_VHF_RQID); ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_SID_VHF_RQID); ++ ++ hid_destroy_device(drvdata->event_ctx.hid); ++ kfree(drvdata); ++ ++ platform_set_drvdata(pdev, NULL); ++ return 0; ++} ++ ++static struct platform_driver surface_sam_sid_vhf = { ++ .probe = surface_sam_sid_vhf_probe, ++ .remove = surface_sam_sid_vhf_remove, ++ .driver = { ++ .name = "surface_sam_sid_vhf", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_sam_sid_vhf); ++ ++MODULE_AUTHOR("Blaž Hrastnik "); ++MODULE_DESCRIPTION("Driver for HID devices connected via Surface SAM"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:surface_sam_sid_vhf"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.c b/drivers/platform/x86/surface_sam/surface_sam_ssh.c +new file mode 100644 +index 000000000000..34905cf29a51 +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.c +@@ -0,0 +1,1773 @@ ++/* ++ * Surface Serial Hub (SSH) driver for communication with the Surface/System ++ * Aggregator Module. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "surface_sam_ssh.h" ++ ++ ++#define SSH_RQST_TAG_FULL "surface_sam_ssh_rqst: " ++#define SSH_RQST_TAG "rqst: " ++#define SSH_EVENT_TAG "event: " ++#define SSH_RECV_TAG "recv: " ++ ++#define SSH_SUPPORTED_FLOW_CONTROL_MASK (~((u8) ACPI_UART_FLOW_CONTROL_HW)) ++ ++#define SSH_BYTELEN_SYNC 2 ++#define SSH_BYTELEN_TERM 2 ++#define SSH_BYTELEN_CRC 2 ++#define SSH_BYTELEN_CTRL 4 // command-header, ACK, or RETRY ++#define SSH_BYTELEN_CMDFRAME 8 // without payload ++ ++#define SSH_MAX_WRITE ( \ ++ SSH_BYTELEN_SYNC \ ++ + SSH_BYTELEN_CTRL \ ++ + SSH_BYTELEN_CRC \ ++ + SSH_BYTELEN_CMDFRAME \ ++ + SURFACE_SAM_SSH_MAX_RQST_PAYLOAD \ ++ + SSH_BYTELEN_CRC \ ++) ++ ++#define SSH_MSG_LEN_CTRL ( \ ++ SSH_BYTELEN_SYNC \ ++ + SSH_BYTELEN_CTRL \ ++ + SSH_BYTELEN_CRC \ ++ + SSH_BYTELEN_TERM \ ++) ++ ++#define SSH_MSG_LEN_CMD_BASE ( \ ++ SSH_BYTELEN_SYNC \ ++ + SSH_BYTELEN_CTRL \ ++ + SSH_BYTELEN_CRC \ ++ + SSH_BYTELEN_CRC \ ++) // without payload and command-frame ++ ++#define SSH_WRITE_TIMEOUT msecs_to_jiffies(1000) ++#define SSH_READ_TIMEOUT msecs_to_jiffies(1000) ++#define SSH_NUM_RETRY 3 ++ ++#define SSH_WRITE_BUF_LEN SSH_MAX_WRITE ++#define SSH_READ_BUF_LEN 512 // must be power of 2 ++#define SSH_EVAL_BUF_LEN SSH_MAX_WRITE // also works for reading ++ ++#define SSH_FRAME_TYPE_CMD_NOACK 0x00 // request/event that does not to be ACKed ++#define SSH_FRAME_TYPE_CMD 0x80 // request/event ++#define SSH_FRAME_TYPE_ACK 0x40 // ACK for request/event ++#define SSH_FRAME_TYPE_RETRY 0x04 // error or retry indicator ++ ++#define SSH_FRAME_OFFS_CTRL SSH_BYTELEN_SYNC ++#define SSH_FRAME_OFFS_CTRL_CRC (SSH_FRAME_OFFS_CTRL + SSH_BYTELEN_CTRL) ++#define SSH_FRAME_OFFS_TERM (SSH_FRAME_OFFS_CTRL_CRC + SSH_BYTELEN_CRC) ++#define SSH_FRAME_OFFS_CMD SSH_FRAME_OFFS_TERM // either TERM or CMD ++#define SSH_FRAME_OFFS_CMD_PLD (SSH_FRAME_OFFS_CMD + SSH_BYTELEN_CMDFRAME) ++ ++/* ++ * A note on Request IDs (RQIDs): ++ * 0x0000 is not a valid RQID ++ * 0x0001 is valid, but reserved for Surface Laptop keyboard events ++ */ ++#define SAM_NUM_EVENT_TYPES ((1 << SURFACE_SAM_SSH_RQID_EVENT_BITS) - 1) ++ ++/* ++ * Sync: aa 55 ++ * Terminate: ff ff ++ * ++ * Request Message: sync cmd-hdr crc(cmd-hdr) cmd-rqst-frame crc(cmd-rqst-frame) ++ * Ack Message: sync ack crc(ack) terminate ++ * Retry Message: sync retry crc(retry) terminate ++ * Response Message: sync cmd-hdr crc(cmd-hdr) cmd-resp-frame crc(cmd-resp-frame) ++ * ++ * Command Header: 80 LEN 00 SEQ ++ * Ack: 40 00 00 SEQ ++ * Retry: 04 00 00 00 ++ * Command Request Frame: 80 RTC 01 00 RIID RQID RCID PLD ++ * Command Response Frame: 80 RTC 00 01 RIID RQID RCID PLD ++ */ ++ ++struct ssh_frame_ctrl { ++ u8 type; ++ u8 len; // without crc ++ u8 pad; ++ u8 seq; ++} __packed; ++ ++struct ssh_frame_cmd { ++ u8 type; ++ u8 tc; ++ u8 pri_out; ++ u8 pri_in; ++ u8 iid; ++ u8 rqid_lo; // id for request/response matching (low byte) ++ u8 rqid_hi; // id for request/response matching (high byte) ++ u8 cid; ++} __packed; ++ ++ ++enum ssh_ec_state { ++ SSH_EC_UNINITIALIZED, ++ SSH_EC_INITIALIZED, ++ SSH_EC_SUSPENDED, ++}; ++ ++struct ssh_counters { ++ u8 seq; // control sequence id ++ u16 rqid; // id for request/response matching ++}; ++ ++struct ssh_writer { ++ u8 *data; ++ u8 *ptr; ++} __packed; ++ ++enum ssh_receiver_state { ++ SSH_RCV_DISCARD, ++ SSH_RCV_CONTROL, ++ SSH_RCV_COMMAND, ++}; ++ ++struct ssh_receiver { ++ spinlock_t lock; ++ enum ssh_receiver_state state; ++ struct completion signal; ++ struct kfifo fifo; ++ struct { ++ bool pld; ++ u8 seq; ++ u16 rqid; ++ } expect; ++ struct { ++ u16 cap; ++ u16 len; ++ u8 *ptr; ++ } eval_buf; ++}; ++ ++struct ssh_event_handler { ++ surface_sam_ssh_event_handler_fn handler; ++ surface_sam_ssh_event_handler_delay delay; ++ void *data; ++}; ++ ++struct ssh_events { ++ spinlock_t lock; ++ struct workqueue_struct *queue_ack; ++ struct workqueue_struct *queue_evt; ++ struct ssh_event_handler handler[SAM_NUM_EVENT_TYPES]; ++}; ++ ++struct sam_ssh_ec { ++ struct mutex lock; ++ enum ssh_ec_state state; ++ struct serdev_device *serdev; ++ struct ssh_counters counter; ++ struct ssh_writer writer; ++ struct ssh_receiver receiver; ++ struct ssh_events events; ++ int irq; ++ bool irq_wakeup_enabled; ++}; ++ ++struct ssh_fifo_packet { ++ u8 type; // packet type (ACK/RETRY/CMD) ++ u8 seq; ++ u8 len; ++}; ++ ++struct ssh_event_work { ++ refcount_t refcount; ++ struct sam_ssh_ec *ec; ++ struct work_struct work_ack; ++ struct delayed_work work_evt; ++ struct surface_sam_ssh_event event; ++ u8 seq; ++}; ++ ++ ++static struct sam_ssh_ec ssh_ec = { ++ .lock = __MUTEX_INITIALIZER(ssh_ec.lock), ++ .state = SSH_EC_UNINITIALIZED, ++ .serdev = NULL, ++ .counter = { ++ .seq = 0, ++ .rqid = 0, ++ }, ++ .writer = { ++ .data = NULL, ++ .ptr = NULL, ++ }, ++ .receiver = { ++ .lock = __SPIN_LOCK_UNLOCKED(), ++ .state = SSH_RCV_DISCARD, ++ .expect = {}, ++ }, ++ .events = { ++ .lock = __SPIN_LOCK_UNLOCKED(), ++ .handler = {}, ++ }, ++ .irq = -1, ++}; ++ ++ ++inline static struct sam_ssh_ec *surface_sam_ssh_acquire(void) ++{ ++ struct sam_ssh_ec *ec = &ssh_ec; ++ ++ mutex_lock(&ec->lock); ++ return ec; ++} ++ ++inline static void surface_sam_ssh_release(struct sam_ssh_ec *ec) ++{ ++ mutex_unlock(&ec->lock); ++} ++ ++inline static struct sam_ssh_ec *surface_sam_ssh_acquire_init(void) ++{ ++ struct sam_ssh_ec *ec = surface_sam_ssh_acquire(); ++ ++ if (ec->state == SSH_EC_UNINITIALIZED) { ++ surface_sam_ssh_release(ec); ++ return NULL; ++ } ++ ++ return ec; ++} ++ ++int surface_sam_ssh_consumer_register(struct device *consumer) ++{ ++ u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; ++ struct sam_ssh_ec *ec; ++ struct device_link *link; ++ ++ ec = surface_sam_ssh_acquire_init(); ++ if (!ec) { ++ return -ENXIO; ++ } ++ ++ link = device_link_add(consumer, &ec->serdev->dev, flags); ++ if (!link) { ++ return -EFAULT; ++ } ++ ++ surface_sam_ssh_release(ec); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(surface_sam_ssh_consumer_register); ++ ++ ++inline static u16 sam_rqid_to_rqst(u16 rqid) { ++ return rqid << SURFACE_SAM_SSH_RQID_EVENT_BITS; ++} ++ ++inline static bool sam_rqid_is_event(u16 rqid) { ++ const u16 mask = (1 << SURFACE_SAM_SSH_RQID_EVENT_BITS) - 1; ++ return rqid != 0 && (rqid | mask) == mask; ++} ++ ++int surface_sam_ssh_enable_event_source(u8 tc, u8 unknown, u16 rqid) ++{ ++ u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 }; ++ u8 buf[1] = { 0x00 }; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = 0x01, ++ .cid = 0x0b, ++ .iid = 0x00, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 0x01, ++ .cdl = 0x04, ++ .pld = pld, ++ }; ++ ++ struct surface_sam_ssh_buf result = { ++ result.cap = ARRAY_SIZE(buf), ++ result.len = 0, ++ result.data = buf, ++ }; ++ ++ int status; ++ ++ // only allow RQIDs that lie within event spectrum ++ if (!sam_rqid_is_event(rqid)) { ++ return -EINVAL; ++ } ++ ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ ++ if (buf[0] != 0x00) { ++ printk(KERN_WARNING SSH_RQST_TAG_FULL ++ "unexpected result while enabling event source: 0x%02x\n", ++ buf[0]); ++ } ++ ++ return status; ++ ++} ++EXPORT_SYMBOL_GPL(surface_sam_ssh_enable_event_source); ++ ++int surface_sam_ssh_disable_event_source(u8 tc, u8 unknown, u16 rqid) ++{ ++ u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 }; ++ u8 buf[1] = { 0x00 }; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = 0x01, ++ .cid = 0x0c, ++ .iid = 0x00, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 0x01, ++ .cdl = 0x04, ++ .pld = pld, ++ }; ++ ++ struct surface_sam_ssh_buf result = { ++ result.cap = ARRAY_SIZE(buf), ++ result.len = 0, ++ result.data = buf, ++ }; ++ ++ int status; ++ ++ // only allow RQIDs that lie within event spectrum ++ if (!sam_rqid_is_event(rqid)) { ++ return -EINVAL; ++ } ++ ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ ++ if (buf[0] != 0x00) { ++ printk(KERN_WARNING SSH_RQST_TAG_FULL ++ "unexpected result while disabling event source: 0x%02x\n", ++ buf[0]); ++ } ++ ++ return status; ++} ++EXPORT_SYMBOL_GPL(surface_sam_ssh_disable_event_source); ++ ++static unsigned long sam_event_default_delay(struct surface_sam_ssh_event *event, void *data) ++{ ++ return event->pri == SURFACE_SAM_PRIORITY_HIGH ? SURFACE_SAM_SSH_EVENT_IMMEDIATE : 0; ++} ++ ++int surface_sam_ssh_set_delayed_event_handler( ++ u16 rqid, surface_sam_ssh_event_handler_fn fn, ++ surface_sam_ssh_event_handler_delay delay, ++ void *data) ++{ ++ struct sam_ssh_ec *ec; ++ unsigned long flags; ++ ++ if (!sam_rqid_is_event(rqid)) { ++ return -EINVAL; ++ } ++ ++ ec = surface_sam_ssh_acquire_init(); ++ if (!ec) { ++ return -ENXIO; ++ } ++ ++ if (!delay) { ++ delay = sam_event_default_delay; ++ } ++ ++ spin_lock_irqsave(&ec->events.lock, flags); ++ // check if we already have a handler ++ if (ec->events.handler[rqid - 1].handler) { ++ spin_unlock_irqrestore(&ec->events.lock, flags); ++ return -EINVAL; ++ } ++ ++ // 0 is not a valid event RQID ++ ec->events.handler[rqid - 1].handler = fn; ++ ec->events.handler[rqid - 1].delay = delay; ++ ec->events.handler[rqid - 1].data = data; ++ ++ spin_unlock_irqrestore(&ec->events.lock, flags); ++ surface_sam_ssh_release(ec); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(surface_sam_ssh_set_delayed_event_handler); ++ ++int surface_sam_ssh_remove_event_handler(u16 rqid) ++{ ++ struct sam_ssh_ec *ec; ++ unsigned long flags; ++ ++ if (!sam_rqid_is_event(rqid)) { ++ return -EINVAL; ++ } ++ ++ ec = surface_sam_ssh_acquire_init(); ++ if (!ec) { ++ return -ENXIO; ++ } ++ ++ spin_lock_irqsave(&ec->events.lock, flags); ++ ++ // 0 is not a valid event RQID ++ ec->events.handler[rqid - 1].handler = NULL; ++ ec->events.handler[rqid - 1].delay = NULL; ++ ec->events.handler[rqid - 1].data = NULL; ++ ++ spin_unlock_irqrestore(&ec->events.lock, flags); ++ surface_sam_ssh_release(ec); ++ ++ /* ++ * Make sure that the handler is not in use any more after we've ++ * removed it. ++ */ ++ flush_workqueue(ec->events.queue_evt); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(surface_sam_ssh_remove_event_handler); ++ ++ ++inline static u16 ssh_crc(const u8 *buf, size_t size) ++{ ++ return crc_ccitt_false(0xffff, buf, size); ++} ++ ++inline static void ssh_write_u16(struct ssh_writer *writer, u16 in) ++{ ++ put_unaligned_le16(in, writer->ptr); ++ writer->ptr += 2; ++} ++ ++inline static void ssh_write_crc(struct ssh_writer *writer, ++ const u8 *buf, size_t size) ++{ ++ ssh_write_u16(writer, ssh_crc(buf, size)); ++} ++ ++inline static void ssh_write_syn(struct ssh_writer *writer) ++{ ++ u8 *w = writer->ptr; ++ ++ *w++ = 0xaa; ++ *w++ = 0x55; ++ ++ writer->ptr = w; ++} ++ ++inline static void ssh_write_ter(struct ssh_writer *writer) ++{ ++ u8 *w = writer->ptr; ++ ++ *w++ = 0xff; ++ *w++ = 0xff; ++ ++ writer->ptr = w; ++} ++ ++inline static void ssh_write_buf(struct ssh_writer *writer, ++ u8 *in, size_t len) ++{ ++ writer->ptr = memcpy(writer->ptr, in, len) + len; ++} ++ ++inline static void ssh_write_hdr(struct ssh_writer *writer, ++ const struct surface_sam_ssh_rqst *rqst, ++ struct sam_ssh_ec *ec) ++{ ++ struct ssh_frame_ctrl *hdr = (struct ssh_frame_ctrl *)writer->ptr; ++ u8 *begin = writer->ptr; ++ ++ hdr->type = SSH_FRAME_TYPE_CMD; ++ hdr->len = SSH_BYTELEN_CMDFRAME + rqst->cdl; // without CRC ++ hdr->pad = 0x00; ++ hdr->seq = ec->counter.seq; ++ ++ writer->ptr += sizeof(*hdr); ++ ++ ssh_write_crc(writer, begin, writer->ptr - begin); ++} ++ ++inline static void ssh_write_cmd(struct ssh_writer *writer, ++ const struct surface_sam_ssh_rqst *rqst, ++ struct sam_ssh_ec *ec) ++{ ++ struct ssh_frame_cmd *cmd = (struct ssh_frame_cmd *)writer->ptr; ++ u8 *begin = writer->ptr; ++ ++ u16 rqid = sam_rqid_to_rqst(ec->counter.rqid); ++ u8 rqid_lo = rqid & 0xFF; ++ u8 rqid_hi = rqid >> 8; ++ ++ cmd->type = SSH_FRAME_TYPE_CMD; ++ cmd->tc = rqst->tc; ++ cmd->pri_out = rqst->pri; ++ cmd->pri_in = 0x00; ++ cmd->iid = rqst->iid; ++ cmd->rqid_lo = rqid_lo; ++ cmd->rqid_hi = rqid_hi; ++ cmd->cid = rqst->cid; ++ ++ writer->ptr += sizeof(*cmd); ++ ++ ssh_write_buf(writer, rqst->pld, rqst->cdl); ++ ssh_write_crc(writer, begin, writer->ptr - begin); ++} ++ ++inline static void ssh_write_ack(struct ssh_writer *writer, u8 seq) ++{ ++ struct ssh_frame_ctrl *ack = (struct ssh_frame_ctrl *)writer->ptr; ++ u8 *begin = writer->ptr; ++ ++ ack->type = SSH_FRAME_TYPE_ACK; ++ ack->len = 0x00; ++ ack->pad = 0x00; ++ ack->seq = seq; ++ ++ writer->ptr += sizeof(*ack); ++ ++ ssh_write_crc(writer, begin, writer->ptr - begin); ++} ++ ++inline static void ssh_writer_reset(struct ssh_writer *writer) ++{ ++ writer->ptr = writer->data; ++} ++ ++inline static int ssh_writer_flush(struct sam_ssh_ec *ec) ++{ ++ struct ssh_writer *writer = &ec->writer; ++ struct serdev_device *serdev = ec->serdev; ++ int status; ++ ++ size_t len = writer->ptr - writer->data; ++ ++ dev_dbg(&ec->serdev->dev, "sending message\n"); ++ print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1, ++ writer->data, writer->ptr - writer->data, false); ++ ++ status = serdev_device_write(serdev, writer->data, len, SSH_WRITE_TIMEOUT); ++ return status >= 0 ? 0 : status; ++} ++ ++inline static void ssh_write_msg_cmd(struct sam_ssh_ec *ec, ++ const struct surface_sam_ssh_rqst *rqst) ++{ ++ ssh_writer_reset(&ec->writer); ++ ssh_write_syn(&ec->writer); ++ ssh_write_hdr(&ec->writer, rqst, ec); ++ ssh_write_cmd(&ec->writer, rqst, ec); ++} ++ ++inline static void ssh_write_msg_ack(struct sam_ssh_ec *ec, u8 seq) ++{ ++ ssh_writer_reset(&ec->writer); ++ ssh_write_syn(&ec->writer); ++ ssh_write_ack(&ec->writer, seq); ++ ssh_write_ter(&ec->writer); ++} ++ ++inline static void ssh_receiver_restart(struct sam_ssh_ec *ec, ++ const struct surface_sam_ssh_rqst *rqst) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ec->receiver.lock, flags); ++ reinit_completion(&ec->receiver.signal); ++ ec->receiver.state = SSH_RCV_CONTROL; ++ ec->receiver.expect.pld = rqst->snc; ++ ec->receiver.expect.seq = ec->counter.seq; ++ ec->receiver.expect.rqid = sam_rqid_to_rqst(ec->counter.rqid); ++ ec->receiver.eval_buf.len = 0; ++ spin_unlock_irqrestore(&ec->receiver.lock, flags); ++} ++ ++inline static void ssh_receiver_discard(struct sam_ssh_ec *ec) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ec->receiver.lock, flags); ++ ec->receiver.state = SSH_RCV_DISCARD; ++ ec->receiver.eval_buf.len = 0; ++ kfifo_reset(&ec->receiver.fifo); ++ spin_unlock_irqrestore(&ec->receiver.lock, flags); ++} ++ ++static int surface_sam_ssh_rqst_unlocked(struct sam_ssh_ec *ec, ++ const struct surface_sam_ssh_rqst *rqst, ++ struct surface_sam_ssh_buf *result) ++{ ++ struct device *dev = &ec->serdev->dev; ++ struct ssh_fifo_packet packet = {}; ++ int status; ++ int try; ++ unsigned int rem; ++ ++ if (rqst->cdl > SURFACE_SAM_SSH_MAX_RQST_PAYLOAD) { ++ dev_err(dev, SSH_RQST_TAG "request payload too large\n"); ++ return -EINVAL; ++ } ++ ++ // write command in buffer, we may need it multiple times ++ ssh_write_msg_cmd(ec, rqst); ++ ssh_receiver_restart(ec, rqst); ++ ++ // send command, try to get an ack response ++ for (try = 0; try < SSH_NUM_RETRY; try++) { ++ status = ssh_writer_flush(ec); ++ if (status) { ++ goto out; ++ } ++ ++ rem = wait_for_completion_timeout(&ec->receiver.signal, SSH_READ_TIMEOUT); ++ if (rem) { ++ // completion assures valid packet, thus ignore returned length ++ (void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet)); ++ ++ if (packet.type == SSH_FRAME_TYPE_ACK) { ++ break; ++ } ++ } ++ } ++ ++ // check if we ran out of tries? ++ if (try >= SSH_NUM_RETRY) { ++ dev_err(dev, SSH_RQST_TAG "communication failed %d times, giving up\n", try); ++ status = -EIO; ++ goto out; ++ } ++ ++ ec->counter.seq += 1; ++ ec->counter.rqid += 1; ++ ++ // get command response/payload ++ if (rqst->snc && result) { ++ rem = wait_for_completion_timeout(&ec->receiver.signal, SSH_READ_TIMEOUT); ++ if (rem) { ++ // completion assures valid packet, thus ignore returned length ++ (void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet)); ++ ++ if (result->cap < packet.len) { ++ status = -EINVAL; ++ goto out; ++ } ++ ++ // completion assures valid packet, thus ignore returned length ++ (void) !kfifo_out(&ec->receiver.fifo, result->data, packet.len); ++ result->len = packet.len; ++ } else { ++ dev_err(dev, SSH_RQST_TAG "communication timed out\n"); ++ status = -EIO; ++ goto out; ++ } ++ ++ // send ACK ++ if (packet.type == SSH_FRAME_TYPE_CMD) { ++ ssh_write_msg_ack(ec, packet.seq); ++ status = ssh_writer_flush(ec); ++ if (status) { ++ goto out; ++ } ++ } ++ } ++ ++out: ++ ssh_receiver_discard(ec); ++ return status; ++} ++ ++int surface_sam_ssh_rqst(const struct surface_sam_ssh_rqst *rqst, struct surface_sam_ssh_buf *result) ++{ ++ struct sam_ssh_ec *ec; ++ int status; ++ ++ ec = surface_sam_ssh_acquire_init(); ++ if (!ec) { ++ printk(KERN_WARNING SSH_RQST_TAG_FULL "embedded controller is uninitialized\n"); ++ return -ENXIO; ++ } ++ ++ if (ec->state == SSH_EC_SUSPENDED) { ++ dev_warn(&ec->serdev->dev, SSH_RQST_TAG "embedded controller is suspended\n"); ++ ++ surface_sam_ssh_release(ec); ++ return -EPERM; ++ } ++ ++ status = surface_sam_ssh_rqst_unlocked(ec, rqst, result); ++ ++ surface_sam_ssh_release(ec); ++ return status; ++} ++EXPORT_SYMBOL_GPL(surface_sam_ssh_rqst); ++ ++ ++static int surface_sam_ssh_ec_resume(struct sam_ssh_ec *ec) ++{ ++ u8 buf[1] = { 0x00 }; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = 0x01, ++ .cid = 0x16, ++ .iid = 0x00, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 0x01, ++ .cdl = 0x00, ++ .pld = NULL, ++ }; ++ ++ struct surface_sam_ssh_buf result = { ++ result.cap = ARRAY_SIZE(buf), ++ result.len = 0, ++ result.data = buf, ++ }; ++ ++ int status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result); ++ if (status) { ++ return status; ++ } ++ ++ if (buf[0] != 0x00) { ++ dev_warn(&ec->serdev->dev, ++ "unexpected result while trying to resume EC: 0x%02x\n", ++ buf[0]); ++ } ++ ++ return 0; ++} ++ ++static int surface_sam_ssh_ec_suspend(struct sam_ssh_ec *ec) ++{ ++ u8 buf[1] = { 0x00 }; ++ ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = 0x01, ++ .cid = 0x15, ++ .iid = 0x00, ++ .pri = SURFACE_SAM_PRIORITY_NORMAL, ++ .snc = 0x01, ++ .cdl = 0x00, ++ .pld = NULL, ++ }; ++ ++ struct surface_sam_ssh_buf result = { ++ result.cap = ARRAY_SIZE(buf), ++ result.len = 0, ++ result.data = buf, ++ }; ++ ++ int status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result); ++ if (status) { ++ return status; ++ } ++ ++ if (buf[0] != 0x00) { ++ dev_warn(&ec->serdev->dev, ++ "unexpected result while trying to suspend EC: 0x%02x\n", ++ buf[0]); ++ } ++ ++ return 0; ++} ++ ++ ++inline static bool ssh_is_valid_syn(const u8 *ptr) ++{ ++ return ptr[0] == 0xaa && ptr[1] == 0x55; ++} ++ ++inline static bool ssh_is_valid_ter(const u8 *ptr) ++{ ++ return ptr[0] == 0xff && ptr[1] == 0xff; ++} ++ ++inline static bool ssh_is_valid_crc(const u8 *begin, const u8 *end) ++{ ++ u16 crc = ssh_crc(begin, end - begin); ++ return (end[0] == (crc & 0xff)) && (end[1] == (crc >> 8)); ++} ++ ++ ++static int surface_sam_ssh_send_ack(struct sam_ssh_ec *ec, u8 seq) ++{ ++ int status; ++ u8 buf[SSH_MSG_LEN_CTRL]; ++ u16 crc; ++ ++ buf[0] = 0xaa; ++ buf[1] = 0x55; ++ buf[2] = 0x40; ++ buf[3] = 0x00; ++ buf[4] = 0x00; ++ buf[5] = seq; ++ ++ crc = ssh_crc(buf + SSH_FRAME_OFFS_CTRL, SSH_BYTELEN_CTRL); ++ buf[6] = crc & 0xff; ++ buf[7] = crc >> 8; ++ ++ buf[8] = 0xff; ++ buf[9] = 0xff; ++ ++ dev_dbg(&ec->serdev->dev, "sending message\n"); ++ print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1, ++ buf, SSH_MSG_LEN_CTRL, false); ++ ++ status = serdev_device_write(ec->serdev, buf, SSH_MSG_LEN_CTRL, SSH_WRITE_TIMEOUT); ++ return status >= 0 ? 0 : status; ++} ++ ++static void surface_sam_ssh_event_work_ack_handler(struct work_struct *_work) ++{ ++ struct surface_sam_ssh_event *event; ++ struct ssh_event_work *work; ++ struct sam_ssh_ec *ec; ++ struct device *dev; ++ int status; ++ ++ work = container_of(_work, struct ssh_event_work, work_ack); ++ event = &work->event; ++ ec = work->ec; ++ dev = &ec->serdev->dev; ++ ++ // make sure we load a fresh ec state ++ smp_mb(); ++ ++ if (ec->state == SSH_EC_INITIALIZED) { ++ status = surface_sam_ssh_send_ack(ec, work->seq); ++ if (status) { ++ dev_err(dev, SSH_EVENT_TAG "failed to send ACK: %d\n", status); ++ } ++ } ++ ++ if (refcount_dec_and_test(&work->refcount)) { ++ kfree(work); ++ } ++} ++ ++static void surface_sam_ssh_event_work_evt_handler(struct work_struct *_work) ++{ ++ struct delayed_work *dwork = (struct delayed_work *)_work; ++ struct ssh_event_work *work; ++ struct surface_sam_ssh_event *event; ++ struct sam_ssh_ec *ec; ++ struct device *dev; ++ unsigned long flags; ++ ++ surface_sam_ssh_event_handler_fn handler; ++ void *handler_data; ++ ++ int status = 0; ++ ++ work = container_of(dwork, struct ssh_event_work, work_evt); ++ event = &work->event; ++ ec = work->ec; ++ dev = &ec->serdev->dev; ++ ++ spin_lock_irqsave(&ec->events.lock, flags); ++ handler = ec->events.handler[event->rqid - 1].handler; ++ handler_data = ec->events.handler[event->rqid - 1].data; ++ spin_unlock_irqrestore(&ec->events.lock, flags); ++ ++ /* ++ * During handler removal or driver release, we ensure every event gets ++ * handled before return of that function. Thus a handler obtained here is ++ * guaranteed to be valid at least until this function returns. ++ */ ++ ++ if (handler) { ++ status = handler(event, handler_data); ++ } else { ++ dev_warn(dev, SSH_EVENT_TAG "unhandled event (rqid: %04x)\n", event->rqid); ++ } ++ ++ if (status) { ++ dev_err(dev, SSH_EVENT_TAG "error handling event: %d\n", status); ++ } ++ ++ if (refcount_dec_and_test(&work->refcount)) { ++ kfree(work); ++ } ++} ++ ++static void ssh_handle_event(struct sam_ssh_ec *ec, const u8 *buf) ++{ ++ struct device *dev = &ec->serdev->dev; ++ const struct ssh_frame_ctrl *ctrl; ++ const struct ssh_frame_cmd *cmd; ++ struct ssh_event_work *work; ++ unsigned long flags; ++ u16 pld_len; ++ ++ surface_sam_ssh_event_handler_delay delay_fn; ++ void *handler_data; ++ unsigned long delay; ++ ++ ctrl = (const struct ssh_frame_ctrl *)(buf + SSH_FRAME_OFFS_CTRL); ++ cmd = (const struct ssh_frame_cmd *)(buf + SSH_FRAME_OFFS_CMD); ++ ++ pld_len = ctrl->len - SSH_BYTELEN_CMDFRAME; ++ ++ work = kzalloc(sizeof(struct ssh_event_work) + pld_len, GFP_ATOMIC); ++ if (!work) { ++ dev_warn(dev, SSH_EVENT_TAG "failed to allocate memory, dropping event\n"); ++ return; ++ } ++ ++ refcount_set(&work->refcount, 1); ++ work->ec = ec; ++ work->seq = ctrl->seq; ++ work->event.rqid = (cmd->rqid_hi << 8) | cmd->rqid_lo; ++ work->event.tc = cmd->tc; ++ work->event.cid = cmd->cid; ++ work->event.iid = cmd->iid; ++ work->event.pri = cmd->pri_in; ++ work->event.len = pld_len; ++ work->event.pld = ((u8*) work) + sizeof(struct ssh_event_work); ++ ++ memcpy(work->event.pld, buf + SSH_FRAME_OFFS_CMD_PLD, pld_len); ++ ++ // queue ACK for if required ++ if (ctrl->type == SSH_FRAME_TYPE_CMD) { ++ refcount_set(&work->refcount, 2); ++ INIT_WORK(&work->work_ack, surface_sam_ssh_event_work_ack_handler); ++ queue_work(ec->events.queue_ack, &work->work_ack); ++ } ++ ++ spin_lock_irqsave(&ec->events.lock, flags); ++ handler_data = ec->events.handler[work->event.rqid - 1].data; ++ delay_fn = ec->events.handler[work->event.rqid - 1].delay; ++ ++ /* Note: ++ * We need to check delay_fn here: This may have never been set as we ++ * can't guarantee that events only occur when they have been enabled. ++ */ ++ delay = delay_fn ? delay_fn(&work->event, handler_data) : 0; ++ spin_unlock_irqrestore(&ec->events.lock, flags); ++ ++ // immediate execution for high priority events (e.g. keyboard) ++ if (delay == SURFACE_SAM_SSH_EVENT_IMMEDIATE) { ++ surface_sam_ssh_event_work_evt_handler(&work->work_evt.work); ++ } else { ++ INIT_DELAYED_WORK(&work->work_evt, surface_sam_ssh_event_work_evt_handler); ++ queue_delayed_work(ec->events.queue_evt, &work->work_evt, delay); ++ } ++} ++ ++static int ssh_receive_msg_ctrl(struct sam_ssh_ec *ec, const u8 *buf, size_t size) ++{ ++ struct device *dev = &ec->serdev->dev; ++ struct ssh_receiver *rcv = &ec->receiver; ++ const struct ssh_frame_ctrl *ctrl; ++ struct ssh_fifo_packet packet; ++ ++ const u8 *ctrl_begin = buf + SSH_FRAME_OFFS_CTRL; ++ const u8 *ctrl_end = buf + SSH_FRAME_OFFS_CTRL_CRC; ++ ++ ctrl = (const struct ssh_frame_ctrl *)(ctrl_begin); ++ ++ // actual length check ++ if (size < SSH_MSG_LEN_CTRL) { ++ return 0; // need more bytes ++ } ++ ++ // validate TERM ++ if (!ssh_is_valid_ter(buf + SSH_FRAME_OFFS_TERM)) { ++ dev_err(dev, SSH_RECV_TAG "invalid end of message\n"); ++ return size; // discard everything ++ } ++ ++ // validate CRC ++ if (!ssh_is_valid_crc(ctrl_begin, ctrl_end)) { ++ dev_err(dev, SSH_RECV_TAG "invalid checksum (ctrl)\n"); ++ return SSH_MSG_LEN_CTRL; // only discard message ++ } ++ ++ // check if we expect the message ++ if (rcv->state != SSH_RCV_CONTROL) { ++ dev_err(dev, SSH_RECV_TAG "discarding message: ctrl not expected\n"); ++ return SSH_MSG_LEN_CTRL; // discard message ++ } ++ ++ // check if it is for our request ++ if (ctrl->type == SSH_FRAME_TYPE_ACK && ctrl->seq != rcv->expect.seq) { ++ dev_err(dev, SSH_RECV_TAG "discarding message: ack does not match\n"); ++ return SSH_MSG_LEN_CTRL; // discard message ++ } ++ ++ // we now have a valid & expected ACK/RETRY message ++ dev_dbg(dev, SSH_RECV_TAG "valid control message received (type: 0x%02x)\n", ctrl->type); ++ ++ packet.type = ctrl->type; ++ packet.seq = ctrl->seq; ++ packet.len = 0; ++ ++ if (kfifo_avail(&rcv->fifo) >= sizeof(packet)) { ++ kfifo_in(&rcv->fifo, (u8 *) &packet, sizeof(packet)); ++ ++ } else { ++ dev_warn(dev, SSH_RECV_TAG ++ "dropping frame: not enough space in fifo (type = %d)\n", ++ ctrl->type); ++ ++ return SSH_MSG_LEN_CTRL; // discard message ++ } ++ ++ // update decoder state ++ if (ctrl->type == SSH_FRAME_TYPE_ACK) { ++ rcv->state = rcv->expect.pld ++ ? SSH_RCV_COMMAND ++ : SSH_RCV_DISCARD; ++ } ++ ++ complete(&rcv->signal); ++ return SSH_MSG_LEN_CTRL; // handled message ++} ++ ++static int ssh_receive_msg_cmd(struct sam_ssh_ec *ec, const u8 *buf, size_t size) ++{ ++ struct device *dev = &ec->serdev->dev; ++ struct ssh_receiver *rcv = &ec->receiver; ++ const struct ssh_frame_ctrl *ctrl; ++ const struct ssh_frame_cmd *cmd; ++ struct ssh_fifo_packet packet; ++ ++ const u8 *ctrl_begin = buf + SSH_FRAME_OFFS_CTRL; ++ const u8 *ctrl_end = buf + SSH_FRAME_OFFS_CTRL_CRC; ++ const u8 *cmd_begin = buf + SSH_FRAME_OFFS_CMD; ++ const u8 *cmd_begin_pld = buf + SSH_FRAME_OFFS_CMD_PLD; ++ const u8 *cmd_end; ++ ++ size_t msg_len; ++ ++ ctrl = (const struct ssh_frame_ctrl *)(ctrl_begin); ++ cmd = (const struct ssh_frame_cmd *)(cmd_begin); ++ ++ // we need at least a full control frame ++ if (size < (SSH_BYTELEN_SYNC + SSH_BYTELEN_CTRL + SSH_BYTELEN_CRC)) { ++ return 0; // need more bytes ++ } ++ ++ // validate control-frame CRC ++ if (!ssh_is_valid_crc(ctrl_begin, ctrl_end)) { ++ dev_err(dev, SSH_RECV_TAG "invalid checksum (cmd-ctrl)\n"); ++ /* ++ * We can't be sure here if length is valid, thus ++ * discard everything. ++ */ ++ return size; ++ } ++ ++ // actual length check (ctrl->len contains command-frame but not crc) ++ msg_len = SSH_MSG_LEN_CMD_BASE + ctrl->len; ++ if (size < msg_len) { ++ return 0; // need more bytes ++ } ++ ++ cmd_end = cmd_begin + ctrl->len; ++ ++ // validate command-frame type ++ if (cmd->type != SSH_FRAME_TYPE_CMD) { ++ dev_err(dev, SSH_RECV_TAG "expected command frame type but got 0x%02x\n", cmd->type); ++ return size; // discard everything ++ } ++ ++ // validate command-frame CRC ++ if (!ssh_is_valid_crc(cmd_begin, cmd_end)) { ++ dev_err(dev, SSH_RECV_TAG "invalid checksum (cmd-pld)\n"); ++ ++ /* ++ * The message length is provided in the control frame. As we ++ * already validated that, we can be sure here that it's ++ * correct, so we only need to discard the message. ++ */ ++ return msg_len; ++ } ++ ++ // check if we received an event notification ++ if (sam_rqid_is_event((cmd->rqid_hi << 8) | cmd->rqid_lo)) { ++ ssh_handle_event(ec, buf); ++ return msg_len; // handled message ++ } ++ ++ // check if we expect the message ++ if (rcv->state != SSH_RCV_COMMAND) { ++ dev_dbg(dev, SSH_RECV_TAG "discarding message: command not expected\n"); ++ return msg_len; // discard message ++ } ++ ++ // check if response is for our request ++ if (rcv->expect.rqid != (cmd->rqid_lo | (cmd->rqid_hi << 8))) { ++ dev_dbg(dev, SSH_RECV_TAG "discarding message: command not a match\n"); ++ return msg_len; // discard message ++ } ++ ++ // we now have a valid & expected command message ++ dev_dbg(dev, SSH_RECV_TAG "valid command message received\n"); ++ ++ packet.type = ctrl->type; ++ packet.seq = ctrl->seq; ++ packet.len = cmd_end - cmd_begin_pld; ++ ++ if (kfifo_avail(&rcv->fifo) >= sizeof(packet) + packet.len) { ++ kfifo_in(&rcv->fifo, &packet, sizeof(packet)); ++ kfifo_in(&rcv->fifo, cmd_begin_pld, packet.len); ++ ++ } else { ++ dev_warn(dev, SSH_RECV_TAG ++ "dropping frame: not enough space in fifo (type = %d)\n", ++ ctrl->type); ++ ++ return SSH_MSG_LEN_CTRL; // discard message ++ } ++ ++ rcv->state = SSH_RCV_DISCARD; ++ ++ complete(&rcv->signal); ++ return msg_len; // handled message ++} ++ ++static int ssh_eval_buf(struct sam_ssh_ec *ec, const u8 *buf, size_t size) ++{ ++ struct device *dev = &ec->serdev->dev; ++ struct ssh_frame_ctrl *ctrl; ++ ++ // we need at least a control frame to check what to do ++ if (size < (SSH_BYTELEN_SYNC + SSH_BYTELEN_CTRL)) { ++ return 0; // need more bytes ++ } ++ ++ // make sure we're actually at the start of a new message ++ if (!ssh_is_valid_syn(buf)) { ++ dev_err(dev, SSH_RECV_TAG "invalid start of message\n"); ++ return size; // discard everything ++ } ++ ++ // handle individual message types seperately ++ ctrl = (struct ssh_frame_ctrl *)(buf + SSH_FRAME_OFFS_CTRL); ++ ++ switch (ctrl->type) { ++ case SSH_FRAME_TYPE_ACK: ++ case SSH_FRAME_TYPE_RETRY: ++ return ssh_receive_msg_ctrl(ec, buf, size); ++ ++ case SSH_FRAME_TYPE_CMD: ++ case SSH_FRAME_TYPE_CMD_NOACK: ++ return ssh_receive_msg_cmd(ec, buf, size); ++ ++ default: ++ dev_err(dev, SSH_RECV_TAG "unknown frame type 0x%02x\n", ctrl->type); ++ return size; // discard everything ++ } ++} ++ ++static int ssh_receive_buf(struct serdev_device *serdev, ++ const unsigned char *buf, size_t size) ++{ ++ struct sam_ssh_ec *ec = serdev_device_get_drvdata(serdev); ++ struct ssh_receiver *rcv = &ec->receiver; ++ unsigned long flags; ++ int offs = 0; ++ int used, n; ++ ++ dev_dbg(&serdev->dev, SSH_RECV_TAG "received buffer (size: %zu)\n", size); ++ print_hex_dump_debug(SSH_RECV_TAG, DUMP_PREFIX_OFFSET, 16, 1, buf, size, false); ++ ++ /* ++ * The battery _BIX message gets a bit long, thus we have to add some ++ * additional buffering here. ++ */ ++ ++ spin_lock_irqsave(&rcv->lock, flags); ++ ++ // copy to eval-buffer ++ used = min(size, (size_t)(rcv->eval_buf.cap - rcv->eval_buf.len)); ++ memcpy(rcv->eval_buf.ptr + rcv->eval_buf.len, buf, used); ++ rcv->eval_buf.len += used; ++ ++ // evaluate buffer until we need more bytes or eval-buf is empty ++ while (offs < rcv->eval_buf.len) { ++ n = rcv->eval_buf.len - offs; ++ n = ssh_eval_buf(ec, rcv->eval_buf.ptr + offs, n); ++ if (n <= 0) break; // need more bytes ++ ++ offs += n; ++ } ++ ++ // throw away the evaluated parts ++ rcv->eval_buf.len -= offs; ++ memmove(rcv->eval_buf.ptr, rcv->eval_buf.ptr + offs, rcv->eval_buf.len); ++ ++ spin_unlock_irqrestore(&rcv->lock, flags); ++ ++ return used; ++} ++ ++ ++#ifdef CONFIG_SURFACE_SAM_SSH_DEBUG_DEVICE ++ ++#include ++ ++static char sam_ssh_debug_rqst_buf_sysfs[SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1] = { 0 }; ++static char sam_ssh_debug_rqst_buf_pld[SURFACE_SAM_SSH_MAX_RQST_PAYLOAD] = { 0 }; ++static char sam_ssh_debug_rqst_buf_res[SURFACE_SAM_SSH_MAX_RQST_RESPONSE] = { 0 }; ++ ++struct sysfs_rqst { ++ u8 tc; ++ u8 cid; ++ u8 iid; ++ u8 pri; ++ u8 snc; ++ u8 cdl; ++ u8 pld[0]; ++} __packed; ++ ++static ssize_t rqst_read(struct file *f, struct kobject *kobj, struct bin_attribute *attr, ++ char *buf, loff_t offs, size_t count) ++{ ++ if (offs < 0 || count + offs > SURFACE_SAM_SSH_MAX_RQST_RESPONSE) { ++ return -EINVAL; ++ } ++ ++ memcpy(buf, sam_ssh_debug_rqst_buf_sysfs + offs, count); ++ return count; ++} ++ ++static ssize_t rqst_write(struct file *f, struct kobject *kobj, struct bin_attribute *attr, ++ char *buf, loff_t offs, size_t count) ++{ ++ struct sysfs_rqst *input; ++ struct surface_sam_ssh_rqst rqst = {}; ++ struct surface_sam_ssh_buf result = {}; ++ int status; ++ ++ // check basic write constriants ++ if (offs != 0 || count > SURFACE_SAM_SSH_MAX_RQST_PAYLOAD + sizeof(struct sysfs_rqst)) { ++ return -EINVAL; ++ } ++ ++ if (count < sizeof(struct sysfs_rqst)) { ++ return -EINVAL; ++ } ++ ++ input = (struct sysfs_rqst *)buf; ++ ++ // payload length should be consistent with data provided ++ if (input->cdl + sizeof(struct sysfs_rqst) != count) { ++ return -EINVAL; ++ } ++ ++ rqst.tc = input->tc; ++ rqst.cid = input->cid; ++ rqst.iid = input->iid; ++ rqst.pri = input->pri; ++ rqst.snc = input->snc; ++ rqst.cdl = input->cdl; ++ rqst.pld = sam_ssh_debug_rqst_buf_pld; ++ memcpy(sam_ssh_debug_rqst_buf_pld, &input->pld[0], input->cdl); ++ ++ result.cap = SURFACE_SAM_SSH_MAX_RQST_RESPONSE; ++ result.len = 0; ++ result.data = sam_ssh_debug_rqst_buf_res; ++ ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ if (status) { ++ return status; ++ } ++ ++ sam_ssh_debug_rqst_buf_sysfs[0] = result.len; ++ memcpy(sam_ssh_debug_rqst_buf_sysfs + 1, result.data, result.len); ++ memset(sam_ssh_debug_rqst_buf_sysfs + result.len + 1, 0, ++ SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1 - result.len); ++ ++ return count; ++} ++ ++static const BIN_ATTR_RW(rqst, SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1); ++ ++ ++int surface_sam_ssh_sysfs_register(struct device *dev) ++{ ++ return sysfs_create_bin_file(&dev->kobj, &bin_attr_rqst); ++} ++ ++void surface_sam_ssh_sysfs_unregister(struct device *dev) ++{ ++ sysfs_remove_bin_file(&dev->kobj, &bin_attr_rqst); ++} ++ ++#else /* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE */ ++ ++int surface_sam_ssh_sysfs_register(struct device *dev) ++{ ++ return 0; ++} ++ ++void surface_sam_ssh_sysfs_unregister(struct device *dev) ++{ ++} ++ ++#endif /* CONFIG_SURFACE_SAM_SSH_DEBUG_DEVICE */ ++ ++ ++static const struct acpi_gpio_params gpio_sam_wakeup_int = { 0, 0, false }; ++static const struct acpi_gpio_params gpio_sam_wakeup = { 1, 0, false }; ++ ++static const struct acpi_gpio_mapping surface_sam_acpi_gpios[] = { ++ { "sam_wakeup-int-gpio", &gpio_sam_wakeup_int, 1 }, ++ { "sam_wakeup-gpio", &gpio_sam_wakeup, 1 }, ++ { }, ++}; ++ ++static irqreturn_t surface_sam_irq_handler(int irq, void *dev_id) ++{ ++ struct serdev_device *serdev = dev_id; ++ ++ dev_info(&serdev->dev, "wake irq triggered\n"); ++ return IRQ_HANDLED; ++} ++ ++static int surface_sam_setup_irq(struct serdev_device *serdev) ++{ ++ const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; ++ struct gpio_desc *gpiod; ++ int irq; ++ int status; ++ ++ gpiod = gpiod_get(&serdev->dev, "sam_wakeup-int", GPIOD_ASIS); ++ if (IS_ERR(gpiod)) ++ return PTR_ERR(gpiod); ++ ++ irq = gpiod_to_irq(gpiod); ++ gpiod_put(gpiod); ++ ++ if (irq < 0) ++ return irq; ++ ++ status = request_threaded_irq(irq, NULL, surface_sam_irq_handler, ++ irqf, "surface_sam_wakeup", serdev); ++ if (status) ++ return status; ++ ++ return irq; ++} ++ ++ ++static acpi_status ++ssh_setup_from_resource(struct acpi_resource *resource, void *context) ++{ ++ struct serdev_device *serdev = context; ++ struct acpi_resource_common_serialbus *serial; ++ struct acpi_resource_uart_serialbus *uart; ++ int status = 0; ++ ++ if (resource->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { ++ return AE_OK; ++ } ++ ++ serial = &resource->data.common_serial_bus; ++ if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) { ++ return AE_OK; ++ } ++ ++ uart = &resource->data.uart_serial_bus; ++ ++ // set up serdev device ++ serdev_device_set_baudrate(serdev, uart->default_baud_rate); ++ ++ // serdev currently only supports RTSCTS flow control ++ if (uart->flow_control & SSH_SUPPORTED_FLOW_CONTROL_MASK) { ++ dev_warn(&serdev->dev, "unsupported flow control (value: 0x%02x)\n", uart->flow_control); ++ } ++ ++ // set RTSCTS flow control ++ serdev_device_set_flow_control(serdev, uart->flow_control & ACPI_UART_FLOW_CONTROL_HW); ++ ++ // serdev currently only supports EVEN/ODD parity ++ switch (uart->parity) { ++ case ACPI_UART_PARITY_NONE: ++ status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); ++ break; ++ case ACPI_UART_PARITY_EVEN: ++ status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN); ++ break; ++ case ACPI_UART_PARITY_ODD: ++ status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD); ++ break; ++ default: ++ dev_warn(&serdev->dev, "unsupported parity (value: 0x%02x)\n", uart->parity); ++ break; ++ } ++ ++ if (status) { ++ dev_err(&serdev->dev, "failed to set parity (value: 0x%02x)\n", uart->parity); ++ return status; ++ } ++ ++ return AE_CTRL_TERMINATE; // we've found the resource and are done ++} ++ ++ ++static int surface_sam_ssh_suspend(struct device *dev) ++{ ++ struct sam_ssh_ec *ec; ++ int status; ++ ++ dev_dbg(dev, "suspending\n"); ++ ++ ec = surface_sam_ssh_acquire_init(); ++ if (ec) { ++ status = surface_sam_ssh_ec_suspend(ec); ++ if (status) { ++ surface_sam_ssh_release(ec); ++ return status; ++ } ++ ++ if (device_may_wakeup(dev)) { ++ status = enable_irq_wake(ec->irq); ++ if (status) { ++ surface_sam_ssh_release(ec); ++ return status; ++ } ++ ++ ec->irq_wakeup_enabled = true; ++ } else { ++ ec->irq_wakeup_enabled = false; ++ } ++ ++ ec->state = SSH_EC_SUSPENDED; ++ surface_sam_ssh_release(ec); ++ } ++ ++ return 0; ++} ++ ++static int surface_sam_ssh_resume(struct device *dev) ++{ ++ struct sam_ssh_ec *ec; ++ int status; ++ ++ dev_dbg(dev, "resuming\n"); ++ ++ ec = surface_sam_ssh_acquire_init(); ++ if (ec) { ++ ec->state = SSH_EC_INITIALIZED; ++ ++ if (ec->irq_wakeup_enabled) { ++ status = disable_irq_wake(ec->irq); ++ if (status) { ++ surface_sam_ssh_release(ec); ++ return status; ++ } ++ ++ ec->irq_wakeup_enabled = false; ++ } ++ ++ status = surface_sam_ssh_ec_resume(ec); ++ if (status) { ++ surface_sam_ssh_release(ec); ++ return status; ++ } ++ ++ surface_sam_ssh_release(ec); ++ } ++ ++ return 0; ++} ++ ++static SIMPLE_DEV_PM_OPS(surface_sam_ssh_pm_ops, surface_sam_ssh_suspend, surface_sam_ssh_resume); ++ ++ ++static const struct serdev_device_ops ssh_device_ops = { ++ .receive_buf = ssh_receive_buf, ++ .write_wakeup = serdev_device_write_wakeup, ++}; ++ ++ ++int surface_sam_ssh_sysfs_register(struct device *dev); ++void surface_sam_ssh_sysfs_unregister(struct device *dev); ++ ++static int surface_sam_ssh_probe(struct serdev_device *serdev) ++{ ++ struct sam_ssh_ec *ec; ++ struct workqueue_struct *event_queue_ack; ++ struct workqueue_struct *event_queue_evt; ++ u8 *write_buf; ++ u8 *read_buf; ++ u8 *eval_buf; ++ acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); ++ acpi_status status; ++ int irq; ++ ++ dev_dbg(&serdev->dev, "probing\n"); ++ ++ if (gpiod_count(&serdev->dev, NULL) < 0) ++ return -ENODEV; ++ ++ status = devm_acpi_dev_add_driver_gpios(&serdev->dev, surface_sam_acpi_gpios); ++ if (status) ++ return status; ++ ++ // allocate buffers ++ write_buf = kzalloc(SSH_WRITE_BUF_LEN, GFP_KERNEL); ++ if (!write_buf) { ++ status = -ENOMEM; ++ goto err_write_buf; ++ } ++ ++ read_buf = kzalloc(SSH_READ_BUF_LEN, GFP_KERNEL); ++ if (!read_buf) { ++ status = -ENOMEM; ++ goto err_read_buf; ++ } ++ ++ eval_buf = kzalloc(SSH_EVAL_BUF_LEN, GFP_KERNEL); ++ if (!eval_buf) { ++ status = -ENOMEM; ++ goto err_eval_buf; ++ } ++ ++ event_queue_ack = create_singlethread_workqueue("surface_sh_ackq"); ++ if (!event_queue_ack) { ++ status = -ENOMEM; ++ goto err_ackq; ++ } ++ ++ event_queue_evt = create_workqueue("surface_sh_evtq"); ++ if (!event_queue_evt) { ++ status = -ENOMEM; ++ goto err_evtq; ++ } ++ ++ irq = surface_sam_setup_irq(serdev); ++ if (irq < 0) { ++ status = irq; ++ goto err_irq; ++ } ++ ++ // set up EC ++ ec = surface_sam_ssh_acquire(); ++ if (ec->state != SSH_EC_UNINITIALIZED) { ++ dev_err(&serdev->dev, "embedded controller already initialized\n"); ++ surface_sam_ssh_release(ec); ++ ++ status = -EBUSY; ++ goto err_busy; ++ } ++ ++ ec->serdev = serdev; ++ ec->irq = irq; ++ ec->writer.data = write_buf; ++ ec->writer.ptr = write_buf; ++ ++ // initialize receiver ++ init_completion(&ec->receiver.signal); ++ kfifo_init(&ec->receiver.fifo, read_buf, SSH_READ_BUF_LEN); ++ ec->receiver.eval_buf.ptr = eval_buf; ++ ec->receiver.eval_buf.cap = SSH_EVAL_BUF_LEN; ++ ec->receiver.eval_buf.len = 0; ++ ++ // initialize event handling ++ ec->events.queue_ack = event_queue_ack; ++ ec->events.queue_evt = event_queue_evt; ++ ++ ec->state = SSH_EC_INITIALIZED; ++ ++ serdev_device_set_drvdata(serdev, ec); ++ ++ // ensure everything is properly set-up before we open the device ++ smp_mb(); ++ ++ serdev_device_set_client_ops(serdev, &ssh_device_ops); ++ status = serdev_device_open(serdev); ++ if (status) { ++ goto err_open; ++ } ++ ++ status = acpi_walk_resources(ssh, METHOD_NAME__CRS, ++ ssh_setup_from_resource, serdev); ++ if (ACPI_FAILURE(status)) { ++ goto err_devinit; ++ } ++ ++ status = surface_sam_ssh_ec_resume(ec); ++ if (status) { ++ goto err_devinit; ++ } ++ ++ status = surface_sam_ssh_sysfs_register(&serdev->dev); ++ if (status) { ++ goto err_devinit; ++ } ++ ++ surface_sam_ssh_release(ec); ++ ++ // TODO: The EC can wake up the system via the associated GPIO interrupt in ++ // multiple situations. One of which is the remaining battery capacity ++ // falling below a certain threshold. Normally, we should use the ++ // device_init_wakeup function, however, the EC also seems to have other ++ // reasons for waking up the system and it seems that Windows has ++ // additional checks whether the system should be resumed. In short, this ++ // causes some spourious unwanted wake-ups. For now let's thus default ++ // power/wakeup to false. ++ device_set_wakeup_capable(&serdev->dev, true); ++ acpi_walk_dep_device_list(ssh); ++ ++ return 0; ++ ++err_devinit: ++ serdev_device_close(serdev); ++err_open: ++ ec->state = SSH_EC_UNINITIALIZED; ++ serdev_device_set_drvdata(serdev, NULL); ++ surface_sam_ssh_release(ec); ++err_busy: ++ free_irq(irq, serdev); ++err_irq: ++ destroy_workqueue(event_queue_evt); ++err_evtq: ++ destroy_workqueue(event_queue_ack); ++err_ackq: ++ kfree(eval_buf); ++err_eval_buf: ++ kfree(read_buf); ++err_read_buf: ++ kfree(write_buf); ++err_write_buf: ++ return status; ++} ++ ++static void surface_sam_ssh_remove(struct serdev_device *serdev) ++{ ++ struct sam_ssh_ec *ec; ++ unsigned long flags; ++ int status; ++ ++ ec = surface_sam_ssh_acquire_init(); ++ if (!ec) { ++ return; ++ } ++ ++ free_irq(ec->irq, serdev); ++ surface_sam_ssh_sysfs_unregister(&serdev->dev); ++ ++ // suspend EC and disable events ++ status = surface_sam_ssh_ec_suspend(ec); ++ if (status) { ++ dev_err(&serdev->dev, "failed to suspend EC: %d\n", status); ++ } ++ ++ // make sure all events (received up to now) have been properly handled ++ flush_workqueue(ec->events.queue_ack); ++ flush_workqueue(ec->events.queue_evt); ++ ++ // remove event handlers ++ spin_lock_irqsave(&ec->events.lock, flags); ++ memset(ec->events.handler, 0, ++ sizeof(struct ssh_event_handler) ++ * SAM_NUM_EVENT_TYPES); ++ spin_unlock_irqrestore(&ec->events.lock, flags); ++ ++ // set device to deinitialized state ++ ec->state = SSH_EC_UNINITIALIZED; ++ ec->serdev = NULL; ++ ++ // ensure state and serdev get set before continuing ++ smp_mb(); ++ ++ /* ++ * Flush any event that has not been processed yet to ensure we're not going to ++ * use the serial device any more (e.g. for ACKing). ++ */ ++ flush_workqueue(ec->events.queue_ack); ++ flush_workqueue(ec->events.queue_evt); ++ ++ serdev_device_close(serdev); ++ ++ /* ++ * Only at this point, no new events can be received. Destroying the ++ * workqueue here flushes all remaining events. Those events will be ++ * silently ignored and neither ACKed nor any handler gets called. ++ */ ++ destroy_workqueue(ec->events.queue_ack); ++ destroy_workqueue(ec->events.queue_evt); ++ ++ // free writer ++ kfree(ec->writer.data); ++ ec->writer.data = NULL; ++ ec->writer.ptr = NULL; ++ ++ // free receiver ++ spin_lock_irqsave(&ec->receiver.lock, flags); ++ ec->receiver.state = SSH_RCV_DISCARD; ++ kfifo_free(&ec->receiver.fifo); ++ ++ kfree(ec->receiver.eval_buf.ptr); ++ ec->receiver.eval_buf.ptr = NULL; ++ ec->receiver.eval_buf.cap = 0; ++ ec->receiver.eval_buf.len = 0; ++ spin_unlock_irqrestore(&ec->receiver.lock, flags); ++ ++ device_set_wakeup_capable(&serdev->dev, false); ++ serdev_device_set_drvdata(serdev, NULL); ++ surface_sam_ssh_release(ec); ++} ++ ++ ++static const struct acpi_device_id surface_sam_ssh_match[] = { ++ { "MSHW0084", 0 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_sam_ssh_match); ++ ++static struct serdev_device_driver surface_sam_ssh = { ++ .probe = surface_sam_ssh_probe, ++ .remove = surface_sam_ssh_remove, ++ .driver = { ++ .name = "surface_sam_ssh", ++ .acpi_match_table = ACPI_PTR(surface_sam_ssh_match), ++ .pm = &surface_sam_ssh_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++static int __init surface_sam_ssh_init(void) ++{ ++ return serdev_device_driver_register(&surface_sam_ssh); ++} ++ ++static void __exit surface_sam_ssh_exit(void) ++{ ++ serdev_device_driver_unregister(&surface_sam_ssh); ++} ++ ++/* ++ * Ensure that the driver is loaded late due to some issues with the UART ++ * communication. Specifically, we want to ensure that DMA is ready and being ++ * used. Not using DMA can result in spurious communication failures, ++ * especially during boot, which among other things will result in wrong ++ * battery information (via ACPI _BIX) being displayed. Using a late init_call ++ * instead of the normal module_init gives the DMA subsystem time to ++ * initialize and via that results in a more stable communication, avoiding ++ * such failures. ++ */ ++late_initcall(surface_sam_ssh_init); ++module_exit(surface_sam_ssh_exit); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface Serial Hub Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.h b/drivers/platform/x86/surface_sam/surface_sam_ssh.h +new file mode 100644 +index 000000000000..714bba6a9457 +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.h +@@ -0,0 +1,97 @@ ++/* ++ * Interface for Surface Serial Hub (SSH). ++ * ++ * The SSH is the main communication hub for communication between host and ++ * the Surface/System Aggregator Module (SAM) on newer Microsoft Surface ++ * devices (Book 2, Pro 5, Laptops, ...). Also referred to as SAM-over-SSH. ++ * Older devices (Book 1, Pro 4) use SAM-over-I2C. ++ */ ++ ++#ifndef _SURFACE_SAM_SSH_H ++#define _SURFACE_SAM_SSH_H ++ ++#include ++#include ++ ++ ++/* ++ * Maximum request payload size in bytes. ++ * Value based on ACPI (255 bytes minus header/status bytes). ++ */ ++#define SURFACE_SAM_SSH_MAX_RQST_PAYLOAD (255 - 10) ++ ++/* ++ * Maximum response payload size in bytes. ++ * Value based on ACPI (255 bytes minus header/status bytes). ++ */ ++#define SURFACE_SAM_SSH_MAX_RQST_RESPONSE (255 - 4) ++ ++/* ++ * The number of (lower) bits of the request ID (RQID) reserved for events. ++ * These bits may only be used exclusively for events sent from the EC to the ++ * host. ++ */ ++#define SURFACE_SAM_SSH_RQID_EVENT_BITS 5 ++ ++/* ++ * Special event-handler delay value indicating that the corresponding event ++ * should be handled immediately in the interrupt and not be relayed through ++ * the workqueue. Intended for low-latency events, such as keyboard events. ++ */ ++#define SURFACE_SAM_SSH_EVENT_IMMEDIATE ((unsigned long) -1) ++ ++ ++#define SURFACE_SAM_PRIORITY_NORMAL 1 ++#define SURFACE_SAM_PRIORITY_HIGH 2 ++ ++ ++struct surface_sam_ssh_buf { ++ u8 cap; ++ u8 len; ++ u8 *data; ++}; ++ ++struct surface_sam_ssh_rqst { ++ u8 tc; // target category ++ u8 cid; // command ID ++ u8 iid; // instance ID ++ u8 pri; // priority ++ u8 snc; // expect response flag ++ u8 cdl; // command data length (lenght of payload) ++ u8 *pld; // pointer to payload of length cdl ++}; ++ ++struct surface_sam_ssh_event { ++ u16 rqid; // event type/source ID ++ u8 tc; // target category ++ u8 cid; // command ID ++ u8 iid; // instance ID ++ u8 pri; // priority ++ u8 len; // length of payload ++ u8 *pld; // payload of length len ++}; ++ ++ ++typedef int (*surface_sam_ssh_event_handler_fn)(struct surface_sam_ssh_event *event, void *data); ++typedef unsigned long (*surface_sam_ssh_event_handler_delay)(struct surface_sam_ssh_event *event, void *data); ++ ++int surface_sam_ssh_consumer_register(struct device *consumer); ++ ++int surface_sam_ssh_rqst(const struct surface_sam_ssh_rqst *rqst, struct surface_sam_ssh_buf *result); ++ ++int surface_sam_ssh_enable_event_source(u8 tc, u8 unknown, u16 rqid); ++int surface_sam_ssh_disable_event_source(u8 tc, u8 unknown, u16 rqid); ++int surface_sam_ssh_remove_event_handler(u16 rqid); ++ ++int surface_sam_ssh_set_delayed_event_handler(u16 rqid, ++ surface_sam_ssh_event_handler_fn fn, ++ surface_sam_ssh_event_handler_delay delay, ++ void *data); ++ ++static inline int surface_sam_ssh_set_event_handler(u16 rqid, surface_sam_ssh_event_handler_fn fn, void *data) ++{ ++ return surface_sam_ssh_set_delayed_event_handler(rqid, fn, NULL, data); ++} ++ ++ ++#endif /* _SURFACE_SAM_SSH_H */ +diff --git a/drivers/platform/x86/surface_sam/surface_sam_vhf.c b/drivers/platform/x86/surface_sam/surface_sam_vhf.c +new file mode 100644 +index 000000000000..0ed0ebbdb3cb +--- /dev/null ++++ b/drivers/platform/x86/surface_sam/surface_sam_vhf.c +@@ -0,0 +1,276 @@ ++/* ++ * Virtual HID Framwork (VHF) driver for input events via SAM. ++ * Used for keyboard input events on the Surface Laptops. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "surface_sam_ssh.h" ++ ++ ++#define USB_VENDOR_ID_MICROSOFT 0x045e ++#define USB_DEVICE_ID_MS_VHF 0xf001 ++ ++#define VHF_INPUT_NAME "Microsoft Virtual HID Framework Device" ++ ++/* ++ * Request ID for VHF events. This value is based on the output of the Surface ++ * EC and should not be changed. ++ */ ++#define SAM_EVENT_VHF_RQID 0x0001 ++#define SAM_EVENT_VHF_TC 0x08 ++ ++ ++struct vhf_evtctx { ++ struct device *dev; ++ struct hid_device *hid; ++}; ++ ++struct vhf_drvdata { ++ struct vhf_evtctx event_ctx; ++}; ++ ++ ++/* ++ * These report descriptors have been extracted from a Surface Book 2. ++ * They seems to be similar enough to be usable on the Surface Laptop. ++ */ ++static const u8 vhf_hid_desc[] = { ++ // keyboard descriptor (event command ID 0x03) ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x06, /* Usage (Keyboard), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x01, /* Report ID (1), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x08, /* Report Count (8), */ ++ 0x05, 0x07, /* Usage Page (Keyboard), */ ++ 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ ++ 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x0A, /* Report Count (10), */ ++ 0x19, 0x00, /* Usage Minimum (None), */ ++ 0x29, 0x91, /* Usage Maximum (KB LANG2), */ ++ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ ++ 0x81, 0x00, /* Input, */ ++ 0x05, 0x0C, /* Usage Page (Consumer), */ ++ 0x0A, 0xC0, 0x02, /* Usage (02C0h), */ ++ 0xA1, 0x02, /* Collection (Logical), */ ++ 0x1A, 0xC1, 0x02, /* Usage Minimum (02C1h), */ ++ 0x2A, 0xC6, 0x02, /* Usage Maximum (02C6h), */ ++ 0x95, 0x06, /* Report Count (6), */ ++ 0xB1, 0x03, /* Feature (Constant, Variable), */ ++ 0xC0, /* End Collection, */ ++ 0x05, 0x08, /* Usage Page (LED), */ ++ 0x19, 0x01, /* Usage Minimum (01h), */ ++ 0x29, 0x03, /* Usage Maximum (03h), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x03, /* Report Count (3), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x91, 0x02, /* Output (Variable), */ ++ 0x95, 0x05, /* Report Count (5), */ ++ 0x91, 0x01, /* Output (Constant), */ ++ 0xC0, /* End Collection, */ ++ ++ // media key descriptor (event command ID 0x04) ++ 0x05, 0x0C, /* Usage Page (Consumer), */ ++ 0x09, 0x01, /* Usage (Consumer Control), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x03, /* Report ID (3), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ ++ 0x19, 0x00, /* Usage Minimum (00h), */ ++ 0x2A, 0xFF, 0x03, /* Usage Maximum (03FFh), */ ++ 0x81, 0x00, /* Input, */ ++ 0xC0, /* End Collection, */ ++}; ++ ++ ++static int vhf_hid_start(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ return 0; ++} ++ ++static void vhf_hid_stop(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++} ++ ++static int vhf_hid_open(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ return 0; ++} ++ ++static void vhf_hid_close(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++} ++ ++static int vhf_hid_parse(struct hid_device *hid) ++{ ++ return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc)); ++} ++ ++static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum, ++ u8 *buf, size_t len, unsigned char rtype, ++ int reqtype) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ return 0; ++} ++ ++static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); ++ ++ return len; ++} ++ ++static struct hid_ll_driver vhf_hid_ll_driver = { ++ .start = vhf_hid_start, ++ .stop = vhf_hid_stop, ++ .open = vhf_hid_open, ++ .close = vhf_hid_close, ++ .parse = vhf_hid_parse, ++ .raw_request = vhf_hid_raw_request, ++ .output_report = vhf_hid_output_report, ++}; ++ ++ ++static struct hid_device *vhf_create_hid_device(struct platform_device *pdev) ++{ ++ struct hid_device *hid; ++ ++ hid = hid_allocate_device(); ++ if (IS_ERR(hid)) { ++ return hid; ++ } ++ ++ hid->dev.parent = &pdev->dev; ++ ++ hid->bus = BUS_VIRTUAL; ++ hid->vendor = USB_VENDOR_ID_MICROSOFT; ++ hid->product = USB_DEVICE_ID_MS_VHF; ++ ++ hid->ll_driver = &vhf_hid_ll_driver; ++ ++ sprintf(hid->name, "%s", VHF_INPUT_NAME); ++ ++ return hid; ++} ++ ++static int vhf_event_handler(struct surface_sam_ssh_event *event, void *data) ++{ ++ struct vhf_evtctx *ctx = (struct vhf_evtctx *)data; ++ ++ if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) { ++ return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1); ++ } ++ ++ dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid); ++ return 0; ++} ++ ++static int surface_sam_vhf_probe(struct platform_device *pdev) ++{ ++ struct vhf_drvdata *drvdata; ++ struct hid_device *hid; ++ int status; ++ ++ // add device link to EC ++ status = surface_sam_ssh_consumer_register(&pdev->dev); ++ if (status) { ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ } ++ ++ drvdata = kzalloc(sizeof(struct vhf_drvdata), GFP_KERNEL); ++ if (!drvdata) { ++ return -ENOMEM; ++ } ++ ++ hid = vhf_create_hid_device(pdev); ++ if (IS_ERR(hid)) { ++ status = PTR_ERR(hid); ++ goto err_probe_hid; ++ } ++ ++ status = hid_add_device(hid); ++ if (status) { ++ goto err_add_hid; ++ } ++ ++ drvdata->event_ctx.dev = &pdev->dev; ++ drvdata->event_ctx.hid = hid; ++ ++ platform_set_drvdata(pdev, drvdata); ++ ++ status = surface_sam_ssh_set_event_handler( ++ SAM_EVENT_VHF_RQID, ++ vhf_event_handler, ++ &drvdata->event_ctx); ++ if (status) { ++ goto err_add_hid; ++ } ++ ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID); ++ if (status) { ++ goto err_event_source; ++ } ++ ++ return 0; ++ ++err_event_source: ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID); ++err_add_hid: ++ hid_destroy_device(hid); ++ platform_set_drvdata(pdev, NULL); ++err_probe_hid: ++ kfree(drvdata); ++ return status; ++} ++ ++static int surface_sam_vhf_remove(struct platform_device *pdev) ++{ ++ struct vhf_drvdata *drvdata = platform_get_drvdata(pdev); ++ ++ surface_sam_ssh_disable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID); ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID); ++ ++ hid_destroy_device(drvdata->event_ctx.hid); ++ kfree(drvdata); ++ ++ platform_set_drvdata(pdev, NULL); ++ return 0; ++} ++ ++ ++static const struct acpi_device_id surface_sam_vhf_match[] = { ++ { "MSHW0096" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_sam_vhf_match); ++ ++static struct platform_driver surface_sam_vhf = { ++ .probe = surface_sam_vhf_probe, ++ .remove = surface_sam_vhf_remove, ++ .driver = { ++ .name = "surface_sam_vhf", ++ .acpi_match_table = ACPI_PTR(surface_sam_vhf_match), ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_sam_vhf); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Virtual HID Framework Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL v2"); +-- +2.25.0 + diff --git a/patches/5.5/0005-surface-lte.patch b/patches/5.5/0005-surface-lte.patch new file mode 100644 index 0000000000..ef128be957 --- /dev/null +++ b/patches/5.5/0005-surface-lte.patch @@ -0,0 +1,24 @@ +From 0ee693298b617d670267684fdfa918aec482e46c Mon Sep 17 00:00:00 2001 +From: qzed +Date: Tue, 17 Sep 2019 17:21:43 +0200 +Subject: [PATCH 5/7] surface-lte + +--- + drivers/usb/serial/qcserial.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c +index 613f91add03d..e1428222dd73 100644 +--- a/drivers/usb/serial/qcserial.c ++++ b/drivers/usb/serial/qcserial.c +@@ -177,6 +177,7 @@ static const struct usb_device_id id_table[] = { + {DEVICE_SWI(0x413c, 0x81d0)}, /* Dell Wireless 5819 */ + {DEVICE_SWI(0x413c, 0x81d1)}, /* Dell Wireless 5818 */ + {DEVICE_SWI(0x413c, 0x81d2)}, /* Dell Wireless 5818 */ ++ {DEVICE_SWI(0x045e, 0x096e)}, /* Microsoft Surface Go LTE */ + + /* Huawei devices */ + {DEVICE_HWI(0x03f0, 0x581d)}, /* HP lt4112 LTE/HSPA+ Gobi 4G Modem (Huawei me906e) */ +-- +2.25.0 + diff --git a/patches/5.5/0006-wifi.patch b/patches/5.5/0006-wifi.patch new file mode 100644 index 0000000000..3fdf642078 --- /dev/null +++ b/patches/5.5/0006-wifi.patch @@ -0,0 +1,168 @@ +From cdab476c49e26bfef7c1f8fab8f272406700fd7b Mon Sep 17 00:00:00 2001 +From: sebanc <22224731+sebanc@users.noreply.github.com> +Date: Mon, 4 Nov 2019 09:30:57 +0100 +Subject: [PATCH 6/7] wifi + +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 74 ++++++++++--------- + .../net/wireless/marvell/mwifiex/sta_cmd.c | 15 +--- + 2 files changed, 41 insertions(+), 48 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index fc1706d0647d..b3380ed75431 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -149,35 +149,38 @@ static bool mwifiex_pcie_ok_to_access_hw(struct mwifiex_adapter *adapter) + */ + static int mwifiex_pcie_suspend(struct device *dev) + { ++ struct pci_dev *pdev = to_pci_dev(dev); ++ struct pcie_service_card *card = pci_get_drvdata(pdev); + struct mwifiex_adapter *adapter; +- struct pcie_service_card *card = dev_get_drvdata(dev); +- ++ struct mwifiex_private *priv; ++ const struct mwifiex_pcie_card_reg *reg; ++ u32 fw_status; ++ int ret; + + /* Might still be loading firmware */ + wait_for_completion(&card->fw_done); + + adapter = card->adapter; +- if (!adapter) { +- dev_err(dev, "adapter is not valid\n"); ++ if (!adapter || !adapter->priv_num) + return 0; +- } + +- mwifiex_enable_wake(adapter); ++ reg = card->pcie.reg; ++ if (reg) ++ ret = mwifiex_read_reg(adapter, reg->fw_status, &fw_status); ++ else ++ fw_status = -1; ++ ++ if (fw_status == FIRMWARE_READY_PCIE && !adapter->mfg_mode) { ++ mwifiex_deauthenticate_all(adapter); + +- /* Enable the Host Sleep */ +- if (!mwifiex_enable_hs(adapter)) { +- mwifiex_dbg(adapter, ERROR, +- "cmd: failed to suspend\n"); +- clear_bit(MWIFIEX_IS_HS_ENABLING, &adapter->work_flags); +- mwifiex_disable_wake(adapter); +- return -EFAULT; +- } ++ priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + +- flush_workqueue(adapter->workqueue); ++ mwifiex_disable_auto_ds(priv); + +- /* Indicate device suspended */ +- set_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); +- clear_bit(MWIFIEX_IS_HS_ENABLING, &adapter->work_flags); ++ mwifiex_init_shutdown_fw(priv, MWIFIEX_FUNC_SHUTDOWN); ++ } ++ ++ mwifiex_remove_card(adapter); + + return 0; + } +@@ -192,28 +195,29 @@ static int mwifiex_pcie_suspend(struct device *dev) + */ + static int mwifiex_pcie_resume(struct device *dev) + { +- struct mwifiex_adapter *adapter; +- struct pcie_service_card *card = dev_get_drvdata(dev); ++ struct pci_dev *pdev = to_pci_dev(dev); ++ struct pcie_service_card *card = pci_get_drvdata(pdev); ++ int ret; + ++ pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", ++ pdev->vendor, pdev->device, pdev->revision); + +- if (!card->adapter) { +- dev_err(dev, "adapter structure is not valid\n"); +- return 0; +- } ++ init_completion(&card->fw_done); + +- adapter = card->adapter; ++ card->dev = pdev; + +- if (!test_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags)) { +- mwifiex_dbg(adapter, WARN, +- "Device already resumed\n"); +- return 0; ++ /* device tree node parsing and platform specific configuration */ ++ if (pdev->dev.of_node) { ++ ret = mwifiex_pcie_probe_of(&pdev->dev); ++ if (ret) ++ return ret; + } + +- clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); +- +- mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA), +- MWIFIEX_ASYNC_CMD); +- mwifiex_disable_wake(adapter); ++ if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, ++ MWIFIEX_PCIE, &pdev->dev)) { ++ pr_err("%s failed\n", __func__); ++ return -1; ++ } + + return 0; + } +@@ -267,6 +271,8 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ pdev->bus->self->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +index 4ed10cf82f9a..013db4386c39 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +@@ -2265,14 +2265,13 @@ int mwifiex_sta_prepare_cmd(struct mwifiex_private *priv, uint16_t cmd_no, + int mwifiex_sta_init_cmd(struct mwifiex_private *priv, u8 first_sta, bool init) + { + struct mwifiex_adapter *adapter = priv->adapter; +- int ret; + struct mwifiex_ds_11n_amsdu_aggr_ctrl amsdu_aggr_ctrl; +- struct mwifiex_ds_auto_ds auto_ds; + enum state_11d_t state_11d; + struct mwifiex_ds_11n_tx_cfg tx_cfg; + u8 sdio_sp_rx_aggr_enable; + u16 packet_aggr_enable; + int data; ++ int ret; + + if (first_sta) { + if (priv->adapter->iface_type == MWIFIEX_PCIE) { +@@ -2395,18 +2394,6 @@ int mwifiex_sta_init_cmd(struct mwifiex_private *priv, u8 first_sta, bool init) + if (ret) + return -1; + +- if (!disable_auto_ds && first_sta && +- priv->bss_type != MWIFIEX_BSS_TYPE_UAP) { +- /* Enable auto deep sleep */ +- auto_ds.auto_ds = DEEP_SLEEP_ON; +- auto_ds.idle_time = DEEP_SLEEP_IDLE_TIME; +- ret = mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, +- EN_AUTO_PS, BITMAP_AUTO_DS, +- &auto_ds, true); +- if (ret) +- return -1; +- } +- + if (priv->bss_type != MWIFIEX_BSS_TYPE_UAP) { + /* Send cmd to FW to enable/disable 11D function */ + state_11d = ENABLE_11D; +-- +2.25.0 + diff --git a/patches/5.5/0007-ipts.patch b/patches/5.5/0007-ipts.patch new file mode 100644 index 0000000000..0e1126c753 --- /dev/null +++ b/patches/5.5/0007-ipts.patch @@ -0,0 +1,1890 @@ +From 3e83aa79daed3c26b4b525cb0a4314f9a3ef6035 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Mon, 27 Jan 2020 21:16:20 +0100 +Subject: [PATCH 7/7] ipts + +--- + drivers/input/touchscreen/Kconfig | 2 + + drivers/input/touchscreen/Makefile | 1 + + drivers/input/touchscreen/ipts/Kconfig | 16 ++ + drivers/input/touchscreen/ipts/Makefile | 16 ++ + drivers/input/touchscreen/ipts/context.h | 61 ++++ + drivers/input/touchscreen/ipts/control.c | 93 ++++++ + drivers/input/touchscreen/ipts/control.h | 18 ++ + drivers/input/touchscreen/ipts/devices.c | 46 +++ + drivers/input/touchscreen/ipts/devices.h | 33 +++ + drivers/input/touchscreen/ipts/hid.c | 122 ++++++++ + drivers/input/touchscreen/ipts/hid.h | 17 ++ + drivers/input/touchscreen/ipts/init.c | 93 ++++++ + drivers/input/touchscreen/ipts/math.c | 103 +++++++ + drivers/input/touchscreen/ipts/math.h | 21 ++ + drivers/input/touchscreen/ipts/params.c | 27 ++ + drivers/input/touchscreen/ipts/params.h | 15 + + .../touchscreen/ipts/protocol/commands.h | 55 ++++ + .../input/touchscreen/ipts/protocol/enums.h | 59 ++++ + .../input/touchscreen/ipts/protocol/events.h | 29 ++ + .../touchscreen/ipts/protocol/responses.h | 40 +++ + .../input/touchscreen/ipts/protocol/touch.h | 62 ++++ + drivers/input/touchscreen/ipts/receiver.c | 266 ++++++++++++++++++ + drivers/input/touchscreen/ipts/receiver.h | 8 + + drivers/input/touchscreen/ipts/resources.c | 131 +++++++++ + drivers/input/touchscreen/ipts/resources.h | 11 + + drivers/input/touchscreen/ipts/singletouch.c | 56 ++++ + drivers/input/touchscreen/ipts/singletouch.h | 13 + + drivers/input/touchscreen/ipts/stylus.c | 159 +++++++++++ + drivers/input/touchscreen/ipts/stylus.h | 13 + + drivers/misc/mei/hw-me-regs.h | 2 + + drivers/misc/mei/pci-me.c | 2 + + include/uapi/linux/input.h | 1 + + 32 files changed, 1591 insertions(+) + create mode 100644 drivers/input/touchscreen/ipts/Kconfig + create mode 100644 drivers/input/touchscreen/ipts/Makefile + create mode 100644 drivers/input/touchscreen/ipts/context.h + create mode 100644 drivers/input/touchscreen/ipts/control.c + create mode 100644 drivers/input/touchscreen/ipts/control.h + create mode 100644 drivers/input/touchscreen/ipts/devices.c + create mode 100644 drivers/input/touchscreen/ipts/devices.h + create mode 100644 drivers/input/touchscreen/ipts/hid.c + create mode 100644 drivers/input/touchscreen/ipts/hid.h + create mode 100644 drivers/input/touchscreen/ipts/init.c + create mode 100644 drivers/input/touchscreen/ipts/math.c + create mode 100644 drivers/input/touchscreen/ipts/math.h + create mode 100644 drivers/input/touchscreen/ipts/params.c + create mode 100644 drivers/input/touchscreen/ipts/params.h + create mode 100644 drivers/input/touchscreen/ipts/protocol/commands.h + create mode 100644 drivers/input/touchscreen/ipts/protocol/enums.h + create mode 100644 drivers/input/touchscreen/ipts/protocol/events.h + create mode 100644 drivers/input/touchscreen/ipts/protocol/responses.h + create mode 100644 drivers/input/touchscreen/ipts/protocol/touch.h + create mode 100644 drivers/input/touchscreen/ipts/receiver.c + create mode 100644 drivers/input/touchscreen/ipts/receiver.h + create mode 100644 drivers/input/touchscreen/ipts/resources.c + create mode 100644 drivers/input/touchscreen/ipts/resources.h + create mode 100644 drivers/input/touchscreen/ipts/singletouch.c + create mode 100644 drivers/input/touchscreen/ipts/singletouch.h + create mode 100644 drivers/input/touchscreen/ipts/stylus.c + create mode 100644 drivers/input/touchscreen/ipts/stylus.h + +diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig +index c071f7c407b6..028ff55d779d 100644 +--- a/drivers/input/touchscreen/Kconfig ++++ b/drivers/input/touchscreen/Kconfig +@@ -1310,4 +1310,6 @@ config TOUCHSCREEN_IQS5XX + To compile this driver as a module, choose M here: the + module will be called iqs5xx. + ++source "drivers/input/touchscreen/ipts/Kconfig" ++ + endif +diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile +index 94c6162409b3..864f0e092ab6 100644 +--- a/drivers/input/touchscreen/Makefile ++++ b/drivers/input/touchscreen/Makefile +@@ -45,6 +45,7 @@ obj-$(CONFIG_TOUCHSCREEN_EXC3000) += exc3000.o + obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o + obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix.o + obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o ++obj-$(CONFIG_TOUCHSCREEN_IPTS) += ipts/ + obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o + obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC) += imx6ul_tsc.o + obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o +diff --git a/drivers/input/touchscreen/ipts/Kconfig b/drivers/input/touchscreen/ipts/Kconfig +new file mode 100644 +index 000000000000..d3c530dafa94 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/Kconfig +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config TOUCHSCREEN_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ select INTEL_MEI ++ depends on X86 ++ depends on PCI ++ depends on HID ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS). ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. +diff --git a/drivers/input/touchscreen/ipts/Makefile b/drivers/input/touchscreen/ipts/Makefile +new file mode 100644 +index 000000000000..d80808175788 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/Makefile +@@ -0,0 +1,16 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_TOUCHSCREEN_IPTS) += ipts.o ++ipts-objs := control.o ++ipts-objs += devices.o ++ipts-objs += hid.o ++ipts-objs += init.o ++ipts-objs += math.o ++ipts-objs += params.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += singletouch.o ++ipts-objs += stylus.o +diff --git a/drivers/input/touchscreen/ipts/context.h b/drivers/input/touchscreen/ipts/context.h +new file mode 100644 +index 000000000000..97a6e13c30c5 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/context.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_CONTEXT_H_ ++#define _IPTS_CONTEXT_H_ ++ ++#include ++#include ++#include ++#include ++ ++#include "devices.h" ++#include "protocol/enums.h" ++#include "protocol/responses.h" ++ ++/* ++ * IPTS driver states ++ */ ++enum ipts_host_status { ++ IPTS_HOST_STATUS_NONE, ++ IPTS_HOST_STATUS_INIT, ++ IPTS_HOST_STATUS_RESOURCE_READY, ++ IPTS_HOST_STATUS_STARTED, ++ IPTS_HOST_STATUS_STOPPING, ++ IPTS_HOST_STATUS_RESTARTING ++}; ++ ++struct ipts_buffer_info { ++ u32 size; ++ u8 *address; ++ dma_addr_t dma_address; ++}; ++ ++struct ipts_context { ++ struct mei_cl_device *client_dev; ++ struct device *dev; ++ struct ipts_device_info device_info; ++ struct ipts_device_config device_cfg; ++ ++ enum ipts_host_status status; ++ enum ipts_sensor_mode mode; ++ ++ struct ipts_buffer_info touch_data[16]; ++ struct ipts_buffer_info feedback[16]; ++ struct ipts_buffer_info doorbell; ++ ++ /* ++ * These buffers are not actually used by anything, but they need ++ * to be allocated and passed to the ME to get proper functionality. ++ */ ++ struct ipts_buffer_info workqueue; ++ struct ipts_buffer_info host2me; ++ ++ struct task_struct *receiver_loop; ++ struct task_struct *hid_loop; ++ ++ struct input_dev *stylus; ++ struct input_dev *singletouch; ++ u16 stylus_tool; ++}; ++ ++#endif /* _IPTS_CONTEXT_H_ */ +diff --git a/drivers/input/touchscreen/ipts/control.c b/drivers/input/touchscreen/ipts/control.c +new file mode 100644 +index 000000000000..66ccea1b0a74 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/control.c +@@ -0,0 +1,93 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++ ++#include "context.h" ++#include "params.h" ++#include "protocol/commands.h" ++#include "protocol/enums.h" ++#include "protocol/events.h" ++#include "protocol/touch.h" ++#include "resources.h" ++ ++int ipts_control_send(struct ipts_context *ipts, ++ u32 cmd, void *data, u32 size) ++{ ++ int ret; ++ struct ipts_command msg; ++ ++ memset(&msg, 0, sizeof(struct ipts_command)); ++ msg.code = cmd; ++ ++ // Copy message payload ++ if (data && size > 0) ++ memcpy(&msg.data, data, size); ++ ++ ret = mei_cldev_send(ipts->client_dev, (u8 *)&msg, ++ sizeof(msg.code) + size); ++ if (ret < 0) { ++ dev_err(ipts->dev, "%s: error 0x%X:%d\n", __func__, cmd, ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, ++ u32 buffer, u32 transaction) ++{ ++ struct ipts_buffer_info feedback_buffer; ++ struct ipts_feedback *feedback; ++ struct ipts_feedback_cmd cmd; ++ ++ feedback_buffer = ipts->feedback[buffer]; ++ feedback = (struct ipts_feedback *)feedback_buffer.address; ++ ++ memset(feedback, 0, sizeof(struct ipts_feedback)); ++ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); ++ ++ feedback->type = IPTS_FEEDBACK_TYPE_NONE; ++ feedback->transaction = transaction; ++ ++ cmd.buffer = buffer; ++ cmd.transaction = transaction; ++ ++ return ipts_control_send(ipts, IPTS_CMD(FEEDBACK), ++ &cmd, sizeof(struct ipts_feedback_cmd)); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ ipts->status = IPTS_HOST_STATUS_INIT; ++ ++ if (ipts_params.singletouch) ++ ipts->mode = IPTS_SENSOR_MODE_SINGLETOUCH; ++ else ++ ipts->mode = IPTS_SENSOR_MODE_MULTITOUCH; ++ ++ return ipts_control_send(ipts, IPTS_CMD(NOTIFY_DEV_READY), NULL, 0); ++} ++ ++void ipts_control_stop(struct ipts_context *ipts) ++{ ++ enum ipts_host_status old_status = ipts->status; ++ ++ ipts->status = IPTS_HOST_STATUS_STOPPING; ++ ipts_control_send(ipts, IPTS_CMD(QUIESCE_IO), NULL, 0); ++ ipts_control_send(ipts, IPTS_CMD(CLEAR_MEM_WINDOW), NULL, 0); ++ ++ if (old_status < IPTS_HOST_STATUS_RESOURCE_READY) ++ return; ++ ++ ipts_resources_free(ipts); ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ dev_info(ipts->dev, "Restarting IPTS\n"); ++ ipts_control_stop(ipts); ++ ++ ipts->status = IPTS_HOST_STATUS_RESTARTING; ++ return ipts_control_send(ipts, IPTS_CMD(QUIESCE_IO), NULL, 0); ++} +diff --git a/drivers/input/touchscreen/ipts/control.h b/drivers/input/touchscreen/ipts/control.h +new file mode 100644 +index 000000000000..e57609c85d62 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/control.h +@@ -0,0 +1,18 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_CONTROL_H_ ++#define _IPTS_CONTROL_H_ ++ ++#include ++ ++#include "context.h" ++ ++int ipts_control_start(struct ipts_context *ipts); ++void ipts_control_stop(struct ipts_context *ipts); ++int ipts_control_restart(struct ipts_context *ipts); ++int ipts_control_send(struct ipts_context *ipts, ++ u32 cmd, void *data, u32 size); ++int ipts_control_send_feedback(struct ipts_context *ipts, ++ u32 buffer, u32 transaction); ++ ++#endif /* _IPTS_CONTROL_H_ */ +diff --git a/drivers/input/touchscreen/ipts/devices.c b/drivers/input/touchscreen/ipts/devices.c +new file mode 100644 +index 000000000000..10593a85e083 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/devices.c +@@ -0,0 +1,46 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++ ++#include "devices.h" ++ ++static const struct ipts_device_config ipts_devices[] = { ++ { ++ .vendor_id = 0x1B96, ++ .device_id = 0x006A, ++ .max_stylus_pressure = 1024, ++ .stylus_protocol = IPTS_STYLUS_PROTOCOL_GEN1, ++ }, ++ { ++ .vendor_id = 0x1B96, ++ .device_id = 0x005e, ++ .max_stylus_pressure = 1024, ++ .stylus_protocol = IPTS_STYLUS_PROTOCOL_GEN1, ++ }, ++}; ++ ++struct ipts_device_config ipts_devices_get_config(u32 vendor, u32 device) ++{ ++ int i; ++ struct ipts_device_config cfg; ++ ++ for (i = 0; i < ARRAY_SIZE(ipts_devices); i++) { ++ cfg = ipts_devices[i]; ++ ++ if (cfg.vendor_id != vendor) ++ continue; ++ if (cfg.device_id != device) ++ continue; ++ ++ return cfg; ++ } ++ ++ // No device was found, so return a default config ++ cfg.vendor_id = vendor; ++ cfg.device_id = device; ++ cfg.max_stylus_pressure = 4096; ++ cfg.stylus_protocol = IPTS_STYLUS_PROTOCOL_GEN2; ++ ++ return cfg; ++} +diff --git a/drivers/input/touchscreen/ipts/devices.h b/drivers/input/touchscreen/ipts/devices.h +new file mode 100644 +index 000000000000..9bf8da9fbfab +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/devices.h +@@ -0,0 +1,33 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_DEVICES_H_ ++#define _IPTS_DEVICES_H_ ++ ++#include ++ ++/* ++ * These names describe the different iterations of the IPTS stylus protocol. ++ * ++ * IPTS_STYLUS_PROTOCOL_GEN1 can be found on devices that don't have ++ * support for tilt, and only 1024 pressure levels. (Using NTRIG digitizers) ++ * ++ * IPTS_STYLUS_PROTOCOL_GEN2 can be found on devices that support tilting ++ * the stylus, with 4096 levels of pressure. (Using MS digitizers) ++ * ++ * New generations have to be added as they are discovered. ++ */ ++enum ipts_stylus_protocol { ++ IPTS_STYLUS_PROTOCOL_GEN1, ++ IPTS_STYLUS_PROTOCOL_GEN2 ++}; ++ ++struct ipts_device_config { ++ u32 vendor_id; ++ u32 device_id; ++ u32 max_stylus_pressure; ++ enum ipts_stylus_protocol stylus_protocol; ++}; ++ ++struct ipts_device_config ipts_devices_get_config(u32 vendor, u32 device); ++ ++#endif /* _IPTS_DEVICES_H_ */ +diff --git a/drivers/input/touchscreen/ipts/hid.c b/drivers/input/touchscreen/ipts/hid.c +new file mode 100644 +index 000000000000..c9f27b0e3b75 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/hid.c +@@ -0,0 +1,122 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "params.h" ++#include "protocol/enums.h" ++#include "protocol/touch.h" ++#include "singletouch.h" ++#include "stylus.h" ++ ++static enum ipts_report_type ipts_hid_parse_report_type( ++ struct ipts_context *ipts, struct ipts_touch_data *data) ++{ ++ // If the buffer contains HID data, we are in single touch mode ++ if (data->type == IPTS_TOUCH_DATA_TYPE_HID_REPORT) ++ return IPTS_REPORT_TYPE_SINGLETOUCH; ++ ++ // If the buffer doesn't contain touch data ++ // we don't care about it ++ if (data->type != IPTS_TOUCH_DATA_TYPE_FRAME) ++ return IPTS_REPORT_TYPE_MAX; ++ ++ // If the number 0x6 is written at offset 14, ++ // the report describes a stylus ++ if (data->data[14] == 0x6) ++ return IPTS_REPORT_TYPE_STYLUS; ++ ++ return IPTS_REPORT_TYPE_MAX; ++} ++ ++static void ipts_hid_handle_input(struct ipts_context *ipts, int buffer_id) ++{ ++ struct ipts_buffer_info buffer; ++ struct ipts_touch_data *data; ++ ++ buffer = ipts->touch_data[buffer_id]; ++ data = (struct ipts_touch_data *)buffer.address; ++ ++ if (ipts_params.debug) { ++ dev_info(ipts->dev, "Buffer %d\n", buffer_id); ++ print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 32, 1, ++ data->data, data->size, false); ++ } ++ ++ switch (ipts_hid_parse_report_type(ipts, data)) { ++ case IPTS_REPORT_TYPE_STYLUS: ++ ipts_stylus_parse_report(ipts, data); ++ break; ++ case IPTS_REPORT_TYPE_SINGLETOUCH: ++ ipts_singletouch_parse_report(ipts, data); ++ break; ++ case IPTS_REPORT_TYPE_MAX: ++ // ignore ++ break; ++ } ++ ++ ipts_control_send_feedback(ipts, buffer_id, data->transaction); ++} ++ ++int ipts_hid_loop(void *data) ++{ ++ time64_t ll_timeout; ++ u32 doorbell, last_doorbell; ++ struct ipts_context *ipts; ++ ++ ll_timeout = ktime_get_seconds() + 5; ++ ipts = (struct ipts_context *)data; ++ last_doorbell = 0; ++ doorbell = 0; ++ ++ dev_info(ipts->dev, "Starting input loop\n"); ++ ++ while (!kthread_should_stop()) { ++ if (ipts->status != IPTS_HOST_STATUS_STARTED) { ++ msleep(1000); ++ continue; ++ } ++ ++ // IPTS will increment the doorbell after it filled up ++ // all of the touch data buffers. If the doorbell didn't ++ // change, there is no work for us to do. ++ doorbell = *(u32 *)ipts->doorbell.address; ++ if (doorbell == last_doorbell) ++ goto sleep; ++ ++ ll_timeout = ktime_get_seconds() + 5; ++ ++ while (last_doorbell != doorbell) { ++ ipts_hid_handle_input(ipts, last_doorbell % 16); ++ last_doorbell++; ++ } ++sleep: ++ if (ll_timeout > ktime_get_seconds()) ++ usleep_range(5000, 30000); ++ else ++ msleep(200); ++ } ++ ++ dev_info(ipts->dev, "Stopping input loop\n"); ++ return 0; ++} ++ ++int ipts_hid_init(struct ipts_context *ipts) ++{ ++ int ret; ++ ++ ret = ipts_stylus_init(ipts); ++ if (ret) ++ return ret; ++ ++ ret = ipts_singletouch_init(ipts); ++ if (ret) ++ return ret; ++ ++ return 0; ++} +diff --git a/drivers/input/touchscreen/ipts/hid.h b/drivers/input/touchscreen/ipts/hid.h +new file mode 100644 +index 000000000000..c6f2ca814c2f +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/hid.h +@@ -0,0 +1,17 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_HID_H_ ++#define _IPTS_HID_H_ ++ ++#include "context.h" ++ ++enum ipts_report_type { ++ IPTS_REPORT_TYPE_STYLUS, ++ IPTS_REPORT_TYPE_SINGLETOUCH, ++ IPTS_REPORT_TYPE_MAX ++}; ++ ++int ipts_hid_init(struct ipts_context *ipts); ++int ipts_hid_loop(void *data); ++ ++#endif /* _IPTS_HID_H_ */ +diff --git a/drivers/input/touchscreen/ipts/init.c b/drivers/input/touchscreen/ipts/init.c +new file mode 100644 +index 000000000000..46f7c7ed1534 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/init.c +@@ -0,0 +1,93 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "receiver.h" ++ ++#define IPTS_MEI_UUID UUID_LE(0x3e8d0870, 0x271a, 0x4208, \ ++ 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++ ++static int ipts_init_probe(struct mei_cl_device *cldev, ++ const struct mei_cl_device_id *id) ++{ ++ int ret; ++ struct ipts_context *ipts = NULL; ++ ++ dev_info(&cldev->dev, "Probing IPTS\n"); ++ ++ // Setup the DMA bit mask ++ if (!dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64))) { ++ dev_info(&cldev->dev, "IPTS using DMA_BIT_MASK(64)\n"); ++ } else if (!dma_coerce_mask_and_coherent(&cldev->dev, ++ DMA_BIT_MASK(32))) { ++ dev_info(&cldev->dev, "IPTS using DMA_BIT_MASK(32)"); ++ } else { ++ dev_err(&cldev->dev, "No suitable DMA for IPTS available\n"); ++ return -EFAULT; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Cannot enable IPTS\n"); ++ return ret; ++ } ++ ++ ipts = devm_kzalloc(&cldev->dev, ++ sizeof(struct ipts_context), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ipts->client_dev = cldev; ++ ipts->dev = &cldev->dev; ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ ++ ipts->receiver_loop = kthread_run(ipts_receiver_loop, (void *)ipts, ++ "ipts_receiver_loop"); ++ ipts->hid_loop = kthread_run(ipts_hid_loop, (void *)ipts, ++ "ipts_hid_loop"); ++ ++ ipts_control_start(ipts); ++ ++ return 0; ++} ++ ++static int ipts_init_remove(struct mei_cl_device *cldev) ++{ ++ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); ++ ++ dev_info(&cldev->dev, "Removing IPTS\n"); ++ ++ ipts_control_stop(ipts); ++ mei_cldev_disable(cldev); ++ kthread_stop(ipts->receiver_loop); ++ kthread_stop(ipts->hid_loop); ++ ++ return 0; ++} ++ ++static struct mei_cl_device_id ipts_device_id[] = { ++ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_device_id); ++ ++static struct mei_cl_driver ipts_driver = { ++ .id_table = ipts_device_id, ++ .name = "ipts", ++ .probe = ipts_init_probe, ++ .remove = ipts_init_remove, ++}; ++module_mei_cl_driver(ipts_driver); ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/touchscreen/ipts/math.c b/drivers/input/touchscreen/ipts/math.c +new file mode 100644 +index 000000000000..df956e5447e0 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/math.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++#include ++ ++#include "math.h" ++ ++/* ++ * Since we need to work with [-pi, pi] in the atan functions, we are using ++ * 1 << 29 for the fixed point numbers. This allows us to store numbers from ++ * [-4, 4] using the full 32-bit signed integer range. ++ * ++ * Some constants such as PI have been already converted to the fixed-point ++ * format and are defined in math.h. ++ */ ++ ++static inline s32 ipts_math_mul(s32 x, s32 y) ++{ ++ return (x * (s64)y) >> 29; ++} ++ ++static inline s32 ipts_math_div(s32 x, s32 y) ++{ ++ return ((s64)x << 29) / y; ++} ++ ++static s32 ipts_math_atan(s32 x) ++{ ++ s32 tmp = ipts_math_mul( ++ ipts_math_mul(x, (abs(x) - (1 << 29))), ++ CONST_2447 + ipts_math_mul(CONST_0663, abs(x))); ++ ++ return ipts_math_mul(M_PI_4, x) - tmp; ++} ++ ++static s32 ipts_math_atan2(s32 y, s32 x) ++{ ++ s32 z; ++ ++ if (x != 0) { ++ if (abs(x) > abs(y)) { ++ z = ipts_math_div(y, x); ++ if (x > 0) ++ return ipts_math_atan(z); ++ else if (y >= 0) ++ return ipts_math_atan(z) + M_PI; ++ else ++ return ipts_math_atan(z) - M_PI; ++ } else { ++ z = ipts_math_div(x, y); ++ if (y > 0) ++ return -ipts_math_atan(z) + M_PI_2; ++ else ++ return -ipts_math_atan(z) - M_PI_2; ++ } ++ } else { ++ if (y > 0) ++ return M_PI_2; ++ else if (y < 0) ++ return -M_PI_2; ++ } ++ ++ return 0; ++} ++ ++/* ++ * Convert altitude in range [0, 9000] and azimuth in range [0, 36000] ++ * to x-/y-tilt in range [-9000, 9000]. Azimuth is given ++ * counter-clockwise, starting with zero on the right. Altitude is ++ * given as angle between stylus and z-axis. ++ */ ++void ipts_math_altitude_azimuth_to_tilt(s32 alt, s32 azm, s32 *tx, s32 *ty) ++{ ++ s32 sin_alt, cos_alt; ++ s32 sin_azm, cos_azm; ++ ++ s32 x, y, z; ++ s64 atan_x, atan_y; ++ ++ sin_alt = fixp_sin32_rad(alt, 36000) / 4; ++ sin_azm = fixp_sin32_rad(azm, 36000) / 4; ++ ++ cos_alt = fixp_cos32_rad(alt, 36000) / 4; ++ cos_azm = fixp_cos32_rad(azm, 36000) / 4; ++ ++ x = ipts_math_mul(sin_alt, cos_azm); ++ y = ipts_math_mul(sin_alt, sin_azm); ++ z = cos_alt; ++ ++ atan_x = ipts_math_atan2(z, x); ++ atan_y = ipts_math_atan2(z, y); ++ ++ atan_x = atan_x * 4500; ++ atan_y = atan_y * 4500; ++ ++ atan_x = atan_x / M_PI_4; ++ atan_y = atan_y / M_PI_4; ++ ++ *tx = 9000 - atan_x; ++ *ty = atan_y - 9000; ++} +diff --git a/drivers/input/touchscreen/ipts/math.h b/drivers/input/touchscreen/ipts/math.h +new file mode 100644 +index 000000000000..8e831074ab60 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/math.h +@@ -0,0 +1,21 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_MATH_H_ ++#define _IPTS_MATH_H_ ++ ++#include ++ ++/* (pi / 4) * (1 << 29) */ ++#define M_PI_4 421657428 ++#define M_PI_2 (M_PI_4 * 2) ++#define M_PI (M_PI_2 * 2) ++ ++/* 0.2447 * (1 << 29) */ ++#define CONST_2447 131372312 ++ ++/* 0.0663 * (1 << 29) */ ++#define CONST_0663 35594541 ++ ++void ipts_math_altitude_azimuth_to_tilt(s32 alt, s32 azm, s32 *tx, s32 *ty); ++ ++#endif /* _IPTS_MATH_H_ */ +diff --git a/drivers/input/touchscreen/ipts/params.c b/drivers/input/touchscreen/ipts/params.c +new file mode 100644 +index 000000000000..6aa3f5cf1d76 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/params.c +@@ -0,0 +1,27 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++ ++#include "params.h" ++ ++#define IPTS_PARM(NAME, TYPE, PERM) \ ++ module_param_named(NAME, ipts_params.NAME, TYPE, PERM) ++ ++#define IPTS_DESC(NAME, DESC) \ ++ MODULE_PARM_DESC(NAME, DESC) ++ ++struct ipts_modparams ipts_params = { ++ .debug = false, ++ .singletouch = false, ++}; ++ ++IPTS_PARM(debug, bool, 0400); ++IPTS_DESC(debug, ++ "Enable additional debugging in the IPTS driver (default: false)" ++); ++ ++IPTS_PARM(singletouch, bool, 0400); ++IPTS_DESC(singletouch, ++ "Enables IPTS single touch mode (disables stylus) (default: false)" ++); +diff --git a/drivers/input/touchscreen/ipts/params.h b/drivers/input/touchscreen/ipts/params.h +new file mode 100644 +index 000000000000..1f992a3bc21b +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/params.h +@@ -0,0 +1,15 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_PARAMS_H_ ++#define _IPTS_PARAMS_H_ ++ ++#include ++ ++struct ipts_modparams { ++ bool debug; ++ bool singletouch; ++}; ++ ++extern struct ipts_modparams ipts_params; ++ ++#endif /* _IPTS_PARAMS_H_ */ +diff --git a/drivers/input/touchscreen/ipts/protocol/commands.h b/drivers/input/touchscreen/ipts/protocol/commands.h +new file mode 100644 +index 000000000000..919a83264d05 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/protocol/commands.h +@@ -0,0 +1,55 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_PROTOCOL_COMMANDS_H_ ++#define _IPTS_PROTOCOL_COMMANDS_H_ ++ ++#include ++#include ++ ++struct ipts_set_mode_cmd { ++ u32 sensor_mode; ++ u8 reserved[12]; ++} __packed; ++ ++struct ipts_set_mem_window_cmd { ++ u32 touch_data_buffer_addr_lower[16]; ++ u32 touch_data_buffer_addr_upper[16]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_buffer_addr_lower[16]; ++ u32 feedback_buffer_addr_upper[16]; ++ u32 host2me_addr_lower; ++ u32 host2me_addr_upper; ++ u32 host2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++struct ipts_feedback_cmd { ++ u32 buffer; ++ u32 transaction; ++ u8 reserved[8]; ++} __packed; ++ ++/* ++ * Commands are sent from the host to the ME ++ */ ++struct ipts_command { ++ u32 code; ++ union { ++ struct ipts_set_mode_cmd set_mode; ++ struct ipts_set_mem_window_cmd set_mem_window; ++ struct ipts_feedback_cmd feedback; ++ } data; ++} __packed; ++ ++static_assert(sizeof(struct ipts_set_mode_cmd) == 16); ++static_assert(sizeof(struct ipts_set_mem_window_cmd) == 320); ++static_assert(sizeof(struct ipts_feedback_cmd) == 16); ++static_assert(sizeof(struct ipts_command) == 324); ++ ++#endif /* _IPTS_PROTOCOL_COMMANDS_H_ */ +diff --git a/drivers/input/touchscreen/ipts/protocol/enums.h b/drivers/input/touchscreen/ipts/protocol/enums.h +new file mode 100644 +index 000000000000..ae952c4cf83c +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/protocol/enums.h +@@ -0,0 +1,59 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_PROTOCOL_ENUMS_H_ ++#define _IPTS_PROTOCOL_ENUMS_H_ ++ ++/* ++ * IPTS ME state ++ */ ++enum ipts_me_status { ++ IPTS_ME_STATUS_SUCCESS = 0, ++ IPTS_ME_STATUS_INVALID_PARAMS, ++ IPTS_ME_STATUS_ACCESS_DENIED, ++ IPTS_ME_STATUS_CMD_SIZE_ERROR, ++ IPTS_ME_STATUS_NOT_READY, ++ IPTS_ME_STATUS_REQUEST_OUTSTANDING, ++ IPTS_ME_STATUS_NO_SENSOR_FOUND, ++ IPTS_ME_STATUS_OUT_OF_MEMORY, ++ IPTS_ME_STATUS_INTERNAL_ERROR, ++ IPTS_ME_STATUS_SENSOR_DISABLED, ++ IPTS_ME_STATUS_COMPAT_CHECK_FAIL, ++ IPTS_ME_STATUS_SENSOR_EXPECTED_RESET, ++ IPTS_ME_STATUS_SENSOR_UNEXPECTED_RESET, ++ IPTS_ME_STATUS_RESET_FAILED, ++ IPTS_ME_STATUS_TIMEOUT, ++ IPTS_ME_STATUS_TEST_MODE_FAIL, ++ IPTS_ME_STATUS_SENSOR_FAIL_FATAL, ++ IPTS_ME_STATUS_SENSOR_FAIL_NONFATAL, ++ IPTS_ME_STATUS_INVALID_DEVICE_CAPS, ++ IPTS_ME_STATUS_QUIESCE_IO_IN_PROGRESS, ++ IPTS_ME_STATUS_MAX ++}; ++ ++enum ipts_sensor_mode { ++ IPTS_SENSOR_MODE_SINGLETOUCH = 0, ++ IPTS_SENSOR_MODE_MULTITOUCH, ++ IPTS_SENSOR_MODE_MAX ++}; ++ ++enum ipts_touch_data_type { ++ IPTS_TOUCH_DATA_TYPE_FRAME = 0, ++ IPTS_TOUCH_DATA_TYPE_ERROR, ++ IPTS_TOUCH_DATA_TYPE_VENDOR_DATA, ++ IPTS_TOUCH_DATA_TYPE_HID_REPORT, ++ IPTS_TOUCH_DATA_TYPE_GET_FEATURES, ++ IPTS_TOUCH_DATA_TYPE_MAX ++}; ++ ++enum ipts_feedback_type { ++ IPTS_FEEDBACK_TYPE_NONE = 0, ++ IPTS_FEEDBACK_TYPE_SOFT_RESET, ++ IPTS_FEEDBACK_TYPE_GOTO_ARMED, ++ IPTS_FEEDBACK_TYPE_GOTO_SENSING, ++ IPTS_FEEDBACK_TYPE_GOTO_SLEEP, ++ IPTS_FEEDBACK_TYPE_GOTO_DOZE, ++ IPTS_FEEDBACK_TYPE_HARD_RESET, ++ IPTS_FEEDBACK_TYPE_MAX ++}; ++ ++#endif /* _IPTS_PROTOCOL_ENUMS_H_ */ +diff --git a/drivers/input/touchscreen/ipts/protocol/events.h b/drivers/input/touchscreen/ipts/protocol/events.h +new file mode 100644 +index 000000000000..f8b771f90bd2 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/protocol/events.h +@@ -0,0 +1,29 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_PROTOCOL_EVENTS_H_ ++#define _IPTS_PROTOCOL_EVENTS_H_ ++ ++/* ++ * Helpers to avoid writing boilerplate code. ++ * The response to a command code is always 0x8000000x, where x ++ * is the command code itself. Instead of writing two definitions, ++ * we use macros to calculate the value on the fly instead. ++ */ ++#define IPTS_CMD(COMMAND) IPTS_EVT_##COMMAND ++#define IPTS_RSP(COMMAND) (IPTS_CMD(COMMAND) + 0x80000000) ++ ++/* ++ * Events that can be sent to / received from the ME ++ */ ++enum ipts_evt_code { ++ IPTS_EVT_GET_DEVICE_INFO = 1, ++ IPTS_EVT_SET_MODE, ++ IPTS_EVT_SET_MEM_WINDOW, ++ IPTS_EVT_QUIESCE_IO, ++ IPTS_EVT_READY_FOR_DATA, ++ IPTS_EVT_FEEDBACK, ++ IPTS_EVT_CLEAR_MEM_WINDOW, ++ IPTS_EVT_NOTIFY_DEV_READY, ++}; ++ ++#endif /* _IPTS_PROTOCOL_EVENTS_H_ */ +diff --git a/drivers/input/touchscreen/ipts/protocol/responses.h b/drivers/input/touchscreen/ipts/protocol/responses.h +new file mode 100644 +index 000000000000..7f8902a1486b +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/protocol/responses.h +@@ -0,0 +1,40 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_PROTOCOL_RESPONSES_H_ ++#define _IPTS_PROTOCOL_RESPONSES_H_ ++ ++#include ++#include ++ ++#include "enums.h" ++ ++struct ipts_device_info { ++ u16 vendor_id; ++ u16 device_id; ++ u32 hw_rev; ++ u32 fw_rev; ++ ++ /* Required size of one touch data buffer */ ++ u32 frame_size; ++ ++ /* Required size of one feedback buffer */ ++ u32 feedback_size; ++ u8 reserved[24]; ++} __packed; ++ ++/* ++ * Responses are sent from the ME to the host, reacting to a command. ++ */ ++struct ipts_response { ++ u32 code; ++ u32 status; ++ union { ++ struct ipts_device_info device_info; ++ u8 reserved[80]; ++ } data; ++} __packed; ++ ++static_assert(sizeof(struct ipts_device_info) == 44); ++static_assert(sizeof(struct ipts_response) == 88); ++ ++#endif /* _IPTS_PROTOCOL_RESPONSES_H_ */ +diff --git a/drivers/input/touchscreen/ipts/protocol/touch.h b/drivers/input/touchscreen/ipts/protocol/touch.h +new file mode 100644 +index 000000000000..86248c015a2e +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/protocol/touch.h +@@ -0,0 +1,62 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_PROTOCOL_TOUCH_H_ ++#define _IPTS_PROTOCOL_TOUCH_H_ ++ ++#include ++ ++struct ipts_touch_data { ++ u32 type; ++ u32 size; ++ u32 buffer; ++ u8 reserved1[20]; ++ u8 transaction; ++ u8 reserved2[31]; ++ u8 data[]; ++} __packed; ++ ++struct ipts_feedback { ++ u32 type; ++ u32 size; ++ u32 transaction; ++ u8 reserved[52]; ++ u8 data[]; ++} __packed; ++ ++struct ipts_stylus_report { ++ u16 timestamp; ++ u16 mode; ++ u16 x; ++ u16 y; ++ u16 pressure; ++ u16 altitude; ++ u16 azimuth; ++ u16 reserved; ++} __packed; ++ ++struct ipts_stylus_report_gen1 { ++ u8 mode; ++ u16 x; ++ u16 y; ++ u16 pressure; ++ u8 reserved[5]; ++} __packed; ++ ++struct ipts_singletouch_report { ++ u8 touch; ++ u16 x; ++ u16 y; ++} __packed; ++ ++#define IPTS_STYLUS_REPORT_MODE_PROXIMITY BIT(0) ++#define IPTS_STYLUS_REPORT_MODE_TOUCH BIT(1) ++#define IPTS_STYLUS_REPORT_MODE_BUTTON BIT(2) ++#define IPTS_STYLUS_REPORT_MODE_RUBBER BIT(3) ++ ++static_assert(sizeof(struct ipts_touch_data) == 64); ++static_assert(sizeof(struct ipts_feedback) == 64); ++static_assert(sizeof(struct ipts_stylus_report) == 16); ++static_assert(sizeof(struct ipts_stylus_report_gen1) == 12); ++static_assert(sizeof(struct ipts_singletouch_report) == 5); ++ ++#endif /* _IPTS_PROTOCOL_TOUCH_H_ */ +diff --git a/drivers/input/touchscreen/ipts/receiver.c b/drivers/input/touchscreen/ipts/receiver.c +new file mode 100644 +index 000000000000..06f4509ff1c4 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/receiver.c +@@ -0,0 +1,266 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "hid.h" ++#include "protocol/commands.h" ++#include "protocol/enums.h" ++#include "protocol/events.h" ++#include "protocol/responses.h" ++#include "resources.h" ++ ++static void ipts_receiver_handle_notify_dev_ready(struct ipts_context *ipts, ++ struct ipts_response *msg, int *cmd_status) ++{ ++ if (msg->status != IPTS_ME_STATUS_SENSOR_FAIL_NONFATAL && ++ msg->status != IPTS_ME_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "0x%08x failed - status = %d\n", ++ msg->code, msg->status); ++ return; ++ } ++ ++ *cmd_status = ipts_control_send(ipts, ++ IPTS_CMD(GET_DEVICE_INFO), NULL, 0); ++} ++ ++static void ipts_receiver_handle_get_device_info(struct ipts_context *ipts, ++ struct ipts_response *msg, int *cmd_status) ++{ ++ if (msg->status != IPTS_ME_STATUS_COMPAT_CHECK_FAIL && ++ msg->status != IPTS_ME_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "0x%08x failed - status = %d\n", ++ msg->code, msg->status); ++ return; ++ } ++ ++ memcpy(&ipts->device_info, &msg->data.device_info, ++ sizeof(struct ipts_device_info)); ++ ++ dev_info(ipts->dev, "Device %04hX:%04hX found\n", ++ ipts->device_info.vendor_id, ++ ipts->device_info.device_id); ++ ++ ipts->device_cfg = ipts_devices_get_config(ipts->device_info.vendor_id, ++ ipts->device_info.device_id); ++ ++ if (ipts_hid_init(ipts)) ++ return; ++ ++ *cmd_status = ipts_control_send(ipts, ++ IPTS_CMD(CLEAR_MEM_WINDOW), NULL, 0); ++} ++ ++static void ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts, ++ struct ipts_response *msg, int *cmd_status, int *ret) ++{ ++ struct ipts_set_mode_cmd sensor_mode_cmd; ++ ++ if (msg->status != IPTS_ME_STATUS_TIMEOUT && ++ msg->status != IPTS_ME_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "0x%08x failed - status = %d\n", ++ msg->code, msg->status); ++ return; ++ } ++ ++ if (ipts->status == IPTS_HOST_STATUS_STOPPING) ++ return; ++ ++ if (ipts_resources_init(ipts)) ++ return; ++ ++ ipts->status = IPTS_HOST_STATUS_RESOURCE_READY; ++ ++ memset(&sensor_mode_cmd, 0, sizeof(struct ipts_set_mode_cmd)); ++ sensor_mode_cmd.sensor_mode = ipts->mode; ++ ++ *cmd_status = ipts_control_send(ipts, IPTS_CMD(SET_MODE), ++ &sensor_mode_cmd, sizeof(struct ipts_set_mode_cmd)); ++} ++ ++static void ipts_receiver_handle_set_mode(struct ipts_context *ipts, ++ struct ipts_response *msg, int *cmd_status) ++{ ++ int i; ++ struct ipts_set_mem_window_cmd cmd; ++ ++ if (msg->status != IPTS_ME_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "0x%08x failed - status = %d\n", ++ msg->code, msg->status); ++ return; ++ } ++ ++ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); ++ ++ for (i = 0; i < 16; i++) { ++ cmd.touch_data_buffer_addr_lower[i] = ++ lower_32_bits(ipts->touch_data[i].dma_address); ++ ++ cmd.touch_data_buffer_addr_upper[i] = ++ upper_32_bits(ipts->touch_data[i].dma_address); ++ ++ cmd.feedback_buffer_addr_lower[i] = ++ lower_32_bits(ipts->feedback[i].dma_address); ++ ++ cmd.feedback_buffer_addr_upper[i] = ++ upper_32_bits(ipts->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); ++ ++ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); ++ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); ++ cmd.host2me_size = ipts->device_info.frame_size; ++ ++ cmd.workqueue_size = 8192; ++ cmd.workqueue_item_size = 16; ++ ++ *cmd_status = ipts_control_send(ipts, IPTS_CMD(SET_MEM_WINDOW), ++ &cmd, sizeof(struct ipts_set_mem_window_cmd)); ++} ++ ++static void ipts_receiver_handle_set_mem_window(struct ipts_context *ipts, ++ struct ipts_response *msg, int *cmd_status) ++{ ++ if (msg->status != IPTS_ME_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "0x%08x failed - status = %d\n", ++ msg->code, msg->status); ++ return; ++ } ++ ++ *cmd_status = ipts_control_send(ipts, ++ IPTS_CMD(READY_FOR_DATA), NULL, 0); ++ if (*cmd_status) ++ return; ++ ++ ipts->status = IPTS_HOST_STATUS_STARTED; ++ dev_info(ipts->dev, "IPTS enabled\n"); ++} ++ ++static void ipts_receiver_handle_ready_for_data(struct ipts_context *ipts, ++ struct ipts_response *msg) ++{ ++ if (msg->status != IPTS_ME_STATUS_SENSOR_DISABLED && ++ msg->status != IPTS_ME_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "0x%08x failed - status = %d\n", ++ msg->code, msg->status); ++ return; ++ } ++ ++ if (ipts->mode != IPTS_SENSOR_MODE_SINGLETOUCH || ++ ipts->status != IPTS_HOST_STATUS_STARTED) ++ return; ++ ++ // Increment the doorbell manually to indicate that a new buffer ++ // filled with touch data is available ++ *((u32 *)ipts->doorbell.address) += 1; ++} ++ ++static void ipts_recever_handle_feedback(struct ipts_context *ipts, ++ struct ipts_response *msg, int *cmd_status) ++{ ++ if (msg->status != IPTS_ME_STATUS_COMPAT_CHECK_FAIL && ++ msg->status != IPTS_ME_STATUS_SUCCESS && ++ msg->status != IPTS_ME_STATUS_INVALID_PARAMS) { ++ dev_err(ipts->dev, "0x%08x failed - status = %d\n", ++ msg->code, msg->status); ++ return; ++ } ++ ++ if (ipts->mode != IPTS_SENSOR_MODE_SINGLETOUCH) ++ return; ++ ++ *cmd_status = ipts_control_send(ipts, ++ IPTS_CMD(READY_FOR_DATA), NULL, 0); ++} ++ ++static void ipts_receiver_handle_quiesce_io(struct ipts_context *ipts, ++ struct ipts_response *msg) ++{ ++ if (msg->status != IPTS_ME_STATUS_SUCCESS) { ++ dev_err(ipts->dev, "0x%08x failed - status = %d\n", ++ msg->code, msg->status); ++ return; ++ } ++ ++ if (ipts->status == IPTS_HOST_STATUS_RESTARTING) ++ ipts_control_start(ipts); ++} ++ ++ ++static int ipts_receiver_handle_response(struct ipts_context *ipts, ++ struct ipts_response *msg, u32 msg_len) ++{ ++ int cmd_status = 0; ++ int ret = 0; ++ ++ switch (msg->code) { ++ case IPTS_RSP(NOTIFY_DEV_READY): ++ ipts_receiver_handle_notify_dev_ready(ipts, msg, &cmd_status); ++ break; ++ case IPTS_RSP(GET_DEVICE_INFO): ++ ipts_receiver_handle_get_device_info(ipts, msg, &cmd_status); ++ break; ++ case IPTS_RSP(CLEAR_MEM_WINDOW): ++ ipts_receiver_handle_clear_mem_window(ipts, msg, ++ &cmd_status, &ret); ++ break; ++ case IPTS_RSP(SET_MODE): ++ ipts_receiver_handle_set_mode(ipts, msg, &cmd_status); ++ break; ++ case IPTS_RSP(SET_MEM_WINDOW): ++ ipts_receiver_handle_set_mem_window(ipts, msg, &cmd_status); ++ break; ++ case IPTS_RSP(READY_FOR_DATA): ++ ipts_receiver_handle_ready_for_data(ipts, msg); ++ break; ++ case IPTS_RSP(FEEDBACK): ++ ipts_recever_handle_feedback(ipts, msg, &cmd_status); ++ break; ++ case IPTS_RSP(QUIESCE_IO): ++ ipts_receiver_handle_quiesce_io(ipts, msg); ++ break; ++ } ++ ++ if (msg->status == IPTS_ME_STATUS_SENSOR_UNEXPECTED_RESET || ++ msg->status == IPTS_ME_STATUS_SENSOR_EXPECTED_RESET) { ++ dev_info(ipts->dev, "Sensor has been reset: %d\n", msg->status); ++ ipts_control_restart(ipts); ++ } ++ ++ if (cmd_status) ++ ipts_control_restart(ipts); ++ ++ return ret; ++} ++ ++int ipts_receiver_loop(void *data) ++{ ++ u32 msg_len; ++ struct ipts_context *ipts; ++ struct ipts_response msg; ++ ++ ipts = (struct ipts_context *)data; ++ dev_info(ipts->dev, "Starting receive loop\n"); ++ ++ while (!kthread_should_stop()) { ++ msg_len = mei_cldev_recv(ipts->client_dev, ++ (u8 *)&msg, sizeof(msg)); ++ ++ if (msg_len <= 0) { ++ dev_err(ipts->dev, "Error in reading ME message\n"); ++ continue; ++ } ++ ++ if (ipts_receiver_handle_response(ipts, &msg, msg_len)) ++ dev_err(ipts->dev, "Error in handling ME message\n"); ++ } ++ ++ dev_info(ipts->dev, "Stopping receive loop\n"); ++ return 0; ++} +diff --git a/drivers/input/touchscreen/ipts/receiver.h b/drivers/input/touchscreen/ipts/receiver.h +new file mode 100644 +index 000000000000..4d413a0abd4c +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/receiver.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_RECEIVER_H_ ++#define _IPTS_RECEIVER_H_ ++ ++int ipts_receiver_loop(void *data); ++ ++#endif /* _IPTS_RECEIVER_H_ */ +diff --git a/drivers/input/touchscreen/ipts/resources.c b/drivers/input/touchscreen/ipts/resources.c +new file mode 100644 +index 000000000000..b4bec3cb63fd +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/resources.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++ ++#include "context.h" ++ ++void ipts_resources_free(struct ipts_context *ipts) ++{ ++ int i; ++ u32 touch_buffer_size; ++ u32 feedback_buffer_size; ++ struct ipts_buffer_info *buffers; ++ ++ touch_buffer_size = ipts->device_info.frame_size; ++ feedback_buffer_size = ipts->device_info.feedback_size; ++ ++ buffers = ipts->touch_data; ++ for (i = 0; i < 16; i++) { ++ if (!buffers[i].address) ++ continue; ++ ++ dmam_free_coherent(ipts->dev, touch_buffer_size, ++ buffers[i].address, buffers[i].dma_address); ++ ++ buffers[i].address = 0; ++ buffers[i].dma_address = 0; ++ } ++ ++ buffers = ipts->feedback; ++ for (i = 0; i < 16; i++) { ++ if (!buffers[i].address) ++ continue; ++ ++ dmam_free_coherent(ipts->dev, feedback_buffer_size, ++ buffers[i].address, buffers[i].dma_address); ++ ++ buffers[i].address = 0; ++ buffers[i].dma_address = 0; ++ } ++ ++ if (ipts->doorbell.address) { ++ dmam_free_coherent(ipts->dev, sizeof(u32), ++ ipts->doorbell.address, ++ ipts->doorbell.dma_address); ++ ++ ipts->doorbell.address = 0; ++ ipts->doorbell.dma_address = 0; ++ } ++ ++ if (ipts->workqueue.address) { ++ dmam_free_coherent(ipts->dev, sizeof(u32), ++ ipts->workqueue.address, ++ ipts->workqueue.dma_address); ++ ++ ipts->workqueue.address = 0; ++ ipts->workqueue.dma_address = 0; ++ } ++ ++ if (ipts->host2me.address) { ++ dmam_free_coherent(ipts->dev, touch_buffer_size, ++ ipts->host2me.address, ++ ipts->host2me.dma_address); ++ ++ ipts->host2me.address = 0; ++ ipts->host2me.dma_address = 0; ++ } ++} ++ ++int ipts_resources_init(struct ipts_context *ipts) ++{ ++ int i; ++ u32 touch_buffer_size; ++ u32 feedback_buffer_size; ++ struct ipts_buffer_info *buffers; ++ ++ touch_buffer_size = ipts->device_info.frame_size; ++ feedback_buffer_size = ipts->device_info.feedback_size; ++ ++ buffers = ipts->touch_data; ++ for (i = 0; i < 16; i++) { ++ buffers[i].address = dmam_alloc_coherent(ipts->dev, ++ touch_buffer_size, ++ &buffers[i].dma_address, ++ GFP_ATOMIC | __GFP_ZERO); ++ ++ if (!buffers[i].address) ++ goto release_resources; ++ } ++ ++ buffers = ipts->feedback; ++ for (i = 0; i < 16; i++) { ++ buffers[i].address = dmam_alloc_coherent(ipts->dev, ++ feedback_buffer_size, ++ &buffers[i].dma_address, ++ GFP_ATOMIC | __GFP_ZERO); ++ ++ if (!buffers[i].address) ++ goto release_resources; ++ } ++ ++ ipts->doorbell.address = dmam_alloc_coherent(ipts->dev, ++ sizeof(u32), ++ &ipts->doorbell.dma_address, ++ GFP_ATOMIC | __GFP_ZERO); ++ ++ if (!ipts->doorbell.address) ++ goto release_resources; ++ ++ ipts->workqueue.address = dmam_alloc_coherent(ipts->dev, ++ sizeof(u32), ++ &ipts->workqueue.dma_address, ++ GFP_ATOMIC | __GFP_ZERO); ++ ++ if (!ipts->workqueue.address) ++ goto release_resources; ++ ++ ipts->host2me.address = dmam_alloc_coherent(ipts->dev, ++ touch_buffer_size, ++ &ipts->host2me.dma_address, ++ GFP_ATOMIC | __GFP_ZERO); ++ ++ if (!ipts->workqueue.address) ++ goto release_resources; ++ ++ return 0; ++ ++release_resources: ++ ++ ipts_resources_free(ipts); ++ return -ENOMEM; ++} +diff --git a/drivers/input/touchscreen/ipts/resources.h b/drivers/input/touchscreen/ipts/resources.h +new file mode 100644 +index 000000000000..cf9807b0dbe6 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/resources.h +@@ -0,0 +1,11 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_RESOURCES_H_ ++#define _IPTS_RESOURCES_H_ ++ ++#include "context.h" ++ ++int ipts_resources_init(struct ipts_context *ipts); ++void ipts_resources_free(struct ipts_context *ipts); ++ ++#endif /* _IPTS_RESOURCES_H_ */ +diff --git a/drivers/input/touchscreen/ipts/singletouch.c b/drivers/input/touchscreen/ipts/singletouch.c +new file mode 100644 +index 000000000000..237dd89f5816 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/singletouch.c +@@ -0,0 +1,56 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++#include "context.h" ++#include "protocol/enums.h" ++#include "protocol/touch.h" ++ ++void ipts_singletouch_parse_report(struct ipts_context *ipts, ++ struct ipts_touch_data *data) ++{ ++ struct ipts_singletouch_report *report = ++ (struct ipts_singletouch_report *)&data->data[1]; ++ ++ input_report_key(ipts->singletouch, BTN_TOUCH, report->touch); ++ input_report_abs(ipts->singletouch, ABS_X, report->x); ++ input_report_abs(ipts->singletouch, ABS_Y, report->y); ++ ++ input_sync(ipts->singletouch); ++} ++ ++int ipts_singletouch_init(struct ipts_context *ipts) ++{ ++ int ret; ++ ++ ipts->singletouch = devm_input_allocate_device(ipts->dev); ++ if (!ipts->singletouch) ++ return -ENOMEM; ++ ++ __set_bit(INPUT_PROP_DIRECT, ipts->singletouch->propbit); ++ ++ input_set_capability(ipts->singletouch, EV_KEY, BTN_TOUCH); ++ input_set_abs_params(ipts->singletouch, ABS_X, 0, 32767, 0, 0); ++ input_abs_set_res(ipts->singletouch, ABS_X, 112); ++ input_set_abs_params(ipts->singletouch, ABS_Y, 0, 32767, 0, 0); ++ input_abs_set_res(ipts->singletouch, ABS_Y, 199); ++ ++ ipts->singletouch->id.bustype = BUS_MEI; ++ ipts->singletouch->id.vendor = ipts->device_info.vendor_id; ++ ipts->singletouch->id.product = ipts->device_info.device_id; ++ ipts->singletouch->id.version = ipts->device_info.fw_rev; ++ ++ ipts->singletouch->phys = "heci3"; ++ ipts->singletouch->name = "Intel Precise Touchscreen (Singletouch)"; ++ ++ ret = input_register_device(ipts->singletouch); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to register touch device: %d\n", ++ ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/input/touchscreen/ipts/singletouch.h b/drivers/input/touchscreen/ipts/singletouch.h +new file mode 100644 +index 000000000000..6bcaf939c8e3 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/singletouch.h +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_SINGLETOUCH_H_ ++#define _IPTS_SINGLETOUCH_H_ ++ ++#include "context.h" ++#include "protocol/touch.h" ++ ++void ipts_singletouch_parse_report(struct ipts_context *ipts, ++ struct ipts_touch_data *data); ++int ipts_singletouch_init(struct ipts_context *ipts); ++ ++#endif /* _IPTS_SINGLETOUCH_H_ */ +diff --git a/drivers/input/touchscreen/ipts/stylus.c b/drivers/input/touchscreen/ipts/stylus.c +new file mode 100644 +index 000000000000..263aa133b706 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/stylus.c +@@ -0,0 +1,159 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++ ++#include "context.h" ++#include "devices.h" ++#include "math.h" ++#include "protocol/enums.h" ++#include "protocol/touch.h" ++ ++static void ipts_stylus_handle_report(struct ipts_context *ipts, ++ struct ipts_stylus_report *report) ++{ ++ u16 tool; ++ u8 prox = report->mode & IPTS_STYLUS_REPORT_MODE_PROXIMITY; ++ u8 touch = report->mode & IPTS_STYLUS_REPORT_MODE_TOUCH; ++ u8 button = report->mode & IPTS_STYLUS_REPORT_MODE_BUTTON; ++ u8 rubber = report->mode & IPTS_STYLUS_REPORT_MODE_RUBBER; ++ ++ s32 tx = 0; ++ s32 ty = 0; ++ ++ // avoid unnecessary computations ++ // altitude is zero if stylus does not touch the screen ++ if (report->altitude) { ++ ipts_math_altitude_azimuth_to_tilt(report->altitude, ++ report->azimuth, &tx, &ty); ++ } ++ ++ if (prox && rubber) ++ tool = BTN_TOOL_RUBBER; ++ else ++ tool = BTN_TOOL_PEN; ++ ++ // Fake proximity out to switch tools ++ if (ipts->stylus_tool != tool) { ++ input_report_key(ipts->stylus, ipts->stylus_tool, 0); ++ input_sync(ipts->stylus); ++ ipts->stylus_tool = tool; ++ } ++ ++ input_report_key(ipts->stylus, BTN_TOUCH, touch); ++ input_report_key(ipts->stylus, ipts->stylus_tool, prox); ++ input_report_key(ipts->stylus, BTN_STYLUS, button); ++ ++ input_report_abs(ipts->stylus, ABS_X, report->x); ++ input_report_abs(ipts->stylus, ABS_Y, report->y); ++ input_report_abs(ipts->stylus, ABS_PRESSURE, report->pressure); ++ input_report_abs(ipts->stylus, ABS_MISC, report->timestamp); ++ ++ input_report_abs(ipts->stylus, ABS_TILT_X, tx); ++ input_report_abs(ipts->stylus, ABS_TILT_Y, ty); ++ ++ input_sync(ipts->stylus); ++} ++ ++static void ipts_stylus_parse_report_gen1(struct ipts_context *ipts, ++ struct ipts_touch_data *data) ++{ ++ u8 count, i; ++ struct ipts_stylus_report report; ++ struct ipts_stylus_report_gen1 *reports; ++ ++ count = data->data[32]; ++ reports = (struct ipts_stylus_report_gen1 *)&data->data[44]; ++ ++ for (i = 0; i < count; i++) { ++ report.mode = reports[i].mode; ++ report.x = reports[i].x; ++ report.y = reports[i].y; ++ report.pressure = reports[i].pressure; ++ ++ // The gen1 protocol doesn't support tilting the stylus ++ report.altitude = 0; ++ report.azimuth = 0; ++ ++ // Use the buffer ID to emulate a timestamp ++ report.timestamp = data->buffer; ++ ++ ipts_stylus_handle_report(ipts, &report); ++ } ++} ++ ++static void ipts_stylus_parse_report_gen2(struct ipts_context *ipts, ++ struct ipts_touch_data *data) ++{ ++ u8 count, i; ++ struct ipts_stylus_report *reports; ++ ++ count = data->data[32]; ++ reports = (struct ipts_stylus_report *)&data->data[40]; ++ ++ for (i = 0; i < count; i++) ++ ipts_stylus_handle_report(ipts, &reports[i]); ++} ++ ++void ipts_stylus_parse_report(struct ipts_context *ipts, ++ struct ipts_touch_data *data) ++{ ++ switch (ipts->device_cfg.stylus_protocol) { ++ case IPTS_STYLUS_PROTOCOL_GEN1: ++ ipts_stylus_parse_report_gen1(ipts, data); ++ break; ++ case IPTS_STYLUS_PROTOCOL_GEN2: ++ ipts_stylus_parse_report_gen2(ipts, data); ++ break; ++ } ++} ++ ++int ipts_stylus_init(struct ipts_context *ipts) ++{ ++ int ret; ++ u16 pressure; ++ ++ ipts->stylus = devm_input_allocate_device(ipts->dev); ++ if (!ipts->stylus) ++ return -ENOMEM; ++ ++ pressure = ipts->device_cfg.max_stylus_pressure; ++ ++ ipts->stylus_tool = BTN_TOOL_PEN; ++ ++ __set_bit(INPUT_PROP_DIRECT, ipts->stylus->propbit); ++ __set_bit(INPUT_PROP_POINTER, ipts->stylus->propbit); ++ ++ input_set_abs_params(ipts->stylus, ABS_X, 0, 9600, 0, 0); ++ input_abs_set_res(ipts->stylus, ABS_X, 34); ++ input_set_abs_params(ipts->stylus, ABS_Y, 0, 7200, 0, 0); ++ input_abs_set_res(ipts->stylus, ABS_Y, 38); ++ input_set_abs_params(ipts->stylus, ABS_PRESSURE, 0, pressure, 0, 0); ++ input_set_abs_params(ipts->stylus, ABS_TILT_X, -9000, 9000, 0, 0); ++ input_abs_set_res(ipts->stylus, ABS_TILT_X, 5730); ++ input_set_abs_params(ipts->stylus, ABS_TILT_Y, -9000, 9000, 0, 0); ++ input_abs_set_res(ipts->stylus, ABS_TILT_Y, 5730); ++ input_set_abs_params(ipts->stylus, ABS_MISC, 0, 65535, 0, 0); ++ input_set_capability(ipts->stylus, EV_KEY, BTN_TOUCH); ++ input_set_capability(ipts->stylus, EV_KEY, BTN_STYLUS); ++ input_set_capability(ipts->stylus, EV_KEY, BTN_TOOL_PEN); ++ input_set_capability(ipts->stylus, EV_KEY, BTN_TOOL_RUBBER); ++ ++ ipts->stylus->id.bustype = BUS_MEI; ++ ipts->stylus->id.vendor = ipts->device_info.vendor_id; ++ ipts->stylus->id.product = ipts->device_info.device_id; ++ ipts->stylus->id.version = ipts->device_info.fw_rev; ++ ++ ipts->stylus->phys = "heci3"; ++ ipts->stylus->name = "Intel Precise Stylus"; ++ ++ ret = input_register_device(ipts->stylus); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to register stylus device: %d\n", ++ ret); ++ return ret; ++ } ++ ++ return 0; ++} +diff --git a/drivers/input/touchscreen/ipts/stylus.h b/drivers/input/touchscreen/ipts/stylus.h +new file mode 100644 +index 000000000000..3b6424ade542 +--- /dev/null ++++ b/drivers/input/touchscreen/ipts/stylus.h +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _IPTS_STYLUS_H_ ++#define _IPTS_STYLUS_H_ ++ ++#include "context.h" ++#include "protocol/touch.h" ++ ++void ipts_stylus_parse_report(struct ipts_context *ipts, ++ struct ipts_touch_data *data); ++int ipts_stylus_init(struct ipts_context *ipts); ++ ++#endif /* _IPTS_STYLUS_H_ */ +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index 7cd67fb2365d..b6c685e861f9 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -59,6 +59,7 @@ + + #define MEI_DEV_ID_SPT 0x9D3A /* Sunrise Point */ + #define MEI_DEV_ID_SPT_2 0x9D3B /* Sunrise Point 2 */ ++#define MEI_DEV_ID_SPT_4 0x9D3E /* Sunrise Point 4 (iTouch) */ + #define MEI_DEV_ID_SPT_H 0xA13A /* Sunrise Point H */ + #define MEI_DEV_ID_SPT_H_2 0xA13B /* Sunrise Point H 2 */ + +@@ -84,6 +85,7 @@ + #define MEI_DEV_ID_CMP_V 0xA3BA /* Comet Point Lake V */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_4 0x34E4 /* Ice Lake Point LP 4 (iTouch) */ + + #define MEI_DEV_ID_TGP_LP 0xA0E0 /* Tiger Lake Point LP */ + +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index c845b7e40f26..0d1a13bb406a 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -77,6 +77,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT, MEI_ME_PCH8_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_2, MEI_ME_PCH8_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_4, MEI_ME_PCH8_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H, MEI_ME_PCH8_SPS_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H_2, MEI_ME_PCH8_SPS_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LBG, MEI_ME_PCH12_CFG)}, +@@ -101,6 +102,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_V, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_4, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, + +diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h +index 9a61c28ed3ae..47fc20975245 100644 +--- a/include/uapi/linux/input.h ++++ b/include/uapi/linux/input.h +@@ -271,6 +271,7 @@ struct input_mask { + #define BUS_RMI 0x1D + #define BUS_CEC 0x1E + #define BUS_INTEL_ISHTP 0x1F ++#define BUS_MEI 0x44 + + /* + * MT_TOOL types +-- +2.25.0 +