diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 566644bb496a..081105a9cfca 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1158,6 +1158,15 @@ config SURFACE_3_BUTTON ---help--- This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. +config ACPI_SURFACE + tristate "Microsoft Surface Extras" + depends on ACPI + depends on ACPI_WMI + depends on INPUT + ---help--- + This driver adds support for access to certain system events + on Microsoft Surface devices. + config INTEL_PUNIT_IPC tristate "Intel P-Unit IPC Driver" ---help--- diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 2ba6cb795338..8fd5b93bb20d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -81,6 +81,9 @@ obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o obj-$(CONFIG_SILEAD_DMI) += silead_dmi.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o +obj-$(CONFIG_ACPI_SURFACE) += surface_acpi.o +obj-$(CONFIG_ACPI_SURFACE) += surface_i2c.o +obj-$(CONFIG_ACPI_SURFACE) += surface_platform.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/surface_acpi.c b/drivers/platform/x86/surface_acpi.c new file mode 100644 index 000000000000..c969bda99464 --- /dev/null +++ b/drivers/platform/x86/surface_acpi.c @@ -0,0 +1,485 @@ +/* + * surface_acpi.c - Microsoft Surface ACPI Driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + */ + +#define SURFACE_ACPI_VERSION "0.1" +#define SURFACE_GEN_VERSION 0x08 +#define PROC_SURFACE "surface" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "surface_acpi.h" + +#define SUR_METHOD_DSM "_DSM" +#define SUR_METHOD_REG "_REG" +#define SUR_METHOD_STA "_STA" +#define SUR_METHOD_INI "_INI" +#define SUR_METHOD_CRS "_CRS" + +#define SUR_QUERY_DEVICE 0x00 +#define SUR_SET_DVER 0x01 +#define SUR_GET_BOARD_REVID 0x02 +#define SUR_BAT1_STATE_CHANGE 0x03 +#define SUR_BAT1_INFO_CHANGE 0x04 +#define SUR_PSU_STATE_CHANGE 0x05 +#define SUR_PSU_INFO_CHANGE 0x06 +#define SUR_BAT2_STATE_CHANGE 0x07 +#define SUR_BAT2_INFO_CHANGE 0x08 +#define SUR_SENSOR_TRIP_POINT 0x09 + +#define REG_AVAILABLE 0x01 +#define REG_INIT 0x09 + +static char SURFACE_EVENT_GUID[] = "93b666c5-70c6-469f-a215-3d487c91ab3c"; +static char SUR_SAN_RQST[] = "\\_SB._SAN.RQST"; +static char SUR_SAN_RQSX[] = "\\_SB._SAN.RQSX"; + +struct surface_acpi_dev { + acpi_handle handle; + acpi_handle rqst_handle; + acpi_handle rqsx_handle; + + struct acpi_device *san_dev; + struct acpi_device *ssh_dev; + struct acpi_device *bat1_dev; + struct acpi_device *bat2_dev; + struct acpi_device *psu_dev; + + unsigned int bat1_attached:1; + unsigned int bat2_attached:1; + unsigned int psu_registered:1; +}; + +static struct surface_acpi_dev *surface_acpi; + +static struct proc_dir_entry *surface_proc_dir; + +static acpi_status surface_acpi_check_status(struct acpi_device *dev) +{ + unsigned long long value; + acpi_status status; + + if (acpi_has_method(dev->handle, SUR_METHOD_STA)) { + status = acpi_evaluate_integer(dev->handle, + SUR_METHOD_STA, NULL, &value); + + if (ACPI_FAILURE(status)) { + pr_err("surface_acpi: ACPI event failure status %s\n", + acpi_format_exception(status)); + return AE_ERROR; + } + } + else + return AE_NOT_FOUND; + + return AE_OK; +} + +static acpi_status surface_acpi_san_reg(void) +{ + union acpi_object in_objs[2], out_objs[1]; + struct acpi_object_list params; + struct acpi_buffer results; + acpi_status status; + + params.count = ARRAY_SIZE(in_objs); + params.pointer = in_objs; + in_objs[0].type = ACPI_TYPE_INTEGER; + in_objs[0].integer.value = REG_INIT; + in_objs[1].type = ACPI_TYPE_INTEGER; + in_objs[1].integer.value = REG_AVAILABLE; + results.length = sizeof(out_objs); + results.pointer = out_objs; + + if (acpi_has_method(surface_acpi->handle, SUR_METHOD_REG)) { + status = acpi_evaluate_object(surface_acpi->handle, + SUR_METHOD_REG, ¶ms, &results); + + if (ACPI_FAILURE(status)) { + pr_err("surface_acpi: ACPI event failure status %s\n", + acpi_format_exception(status)); + return AE_ERROR; + } + } + else + return AE_NOT_FOUND; + + return AE_OK; +} + +acpi_status surface_acpi_event_handler(u32 event) +{ + union acpi_object in_objs[4], out_objs[5]; + struct acpi_object_list params; + struct acpi_buffer results; + acpi_status status; + + params.count = ARRAY_SIZE(in_objs); + params.pointer = in_objs; + in_objs[0].type = ACPI_TYPE_BUFFER; + in_objs[0].buffer.length = sizeof(SURFACE_EVENT_GUID); + in_objs[0].buffer.pointer = SURFACE_EVENT_GUID; + in_objs[1].type = ACPI_TYPE_INTEGER; + in_objs[1].integer.value = SUR_QUERY_DEVICE; + in_objs[2].type = ACPI_TYPE_INTEGER; + in_objs[2].integer.value = event; + in_objs[3].type = ACPI_TYPE_PACKAGE; + in_objs[3].package.count = 0; + in_objs[3].package.elements = SURFACE_GEN_VERSION; + results.length = sizeof(out_objs); + results.pointer = out_objs; + + if (acpi_has_method(surface_acpi->handle, SUR_METHOD_DSM)) { + status = acpi_evaluate_object(surface_acpi->handle, + SUR_METHOD_DSM, ¶ms, &results); + + if (ACPI_FAILURE(status)) { + pr_err("surface_acpi: ACPI event failure status %s\n", + acpi_format_exception(status)); + return AE_ERROR; + } + } + else + return AE_NOT_FOUND; + + return AE_OK; +} +EXPORT_SYMBOL(surface_acpi_event_handler); + +static void surface_acpi_san_load(void) +{ + acpi_status ret; + + ret = surface_acpi_event_handler(SUR_SET_DVER); + if (ACPI_FAILURE(ret)) + pr_err("surface_acpi: Error setting Driver Version\n"); + + ret = surface_acpi_event_handler(SUR_SENSOR_TRIP_POINT); + if (ACPI_FAILURE(ret)) + pr_err("surface_acpi: Error setting Sensor Trip Point\n"); + + ret = surface_acpi_event_handler(SUR_BAT1_INFO_CHANGE); + if (ACPI_FAILURE(ret)) + pr_err("surface_acpi: Error attaching BAT1\n"); + else + surface_acpi->bat1_attached = 1; + + ret = surface_acpi_event_handler(SUR_BAT2_INFO_CHANGE); + if (ACPI_FAILURE(ret)) + pr_err("surface_acpi: Error attaching BAT2\n"); + else + surface_acpi->bat2_attached = 1; + + ret = surface_acpi_event_handler(SUR_PSU_INFO_CHANGE); + if (ACPI_FAILURE(ret)) + pr_err("surface_acpi: Error registering PSU\n"); + else + surface_acpi->psu_registered = 1; +} + +static acpi_status surface_acpi_ssh_initialize(void) +{ + acpi_status status; + + if (acpi_has_method(surface_acpi->ssh_dev->handle, SUR_METHOD_INI)) { + status = acpi_evaluate_object(surface_acpi->ssh_dev->handle, + SUR_METHOD_INI, NULL, NULL); + + if (ACPI_FAILURE(status)) { + pr_err("surface_acpi: ACPI event failure status %s\n", + acpi_format_exception(status)); + return AE_ERROR; + } + } + else + return AE_NOT_FOUND; + + return AE_OK; +} + +static int bat1_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "attached: %d\n", surface_acpi->bat1_attached); + return 0; +} + +static int bat1_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, bat1_proc_show, PDE_DATA(inode)); +} + +static const struct file_operations bat1_proc_fops = { + .owner = THIS_MODULE, + .open = bat1_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int bat2_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "attached: %d\n", surface_acpi->bat2_attached); + return 0; +} + +static int bat2_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, bat2_proc_show, PDE_DATA(inode)); +} + +static const struct file_operations bat2_proc_fops = { + .owner = THIS_MODULE, + .open = bat2_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int psu_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "registered: %d\n", surface_acpi->psu_registered); + return 0; +} + +static int psu_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, psu_proc_show, PDE_DATA(inode)); +} + +static const struct file_operations psu_proc_fops = { + .owner = THIS_MODULE, + .open = psu_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int version_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "driver: %s\n", SURFACE_ACPI_VERSION); + return 0; +} + +static int version_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, version_proc_show, PDE_DATA(inode)); +} + +static const struct file_operations version_proc_fops = { + .owner = THIS_MODULE, + .open = version_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void create_surface_proc_entries(void) +{ + proc_create_data("BAT1", 0, surface_proc_dir, + &bat1_proc_fops, surface_acpi->bat1_attached); + proc_create_data("BAT2", 0, surface_proc_dir, + &bat2_proc_fops, surface_acpi->bat2_attached); + proc_create_data("ADP1", 0, surface_proc_dir, + &psu_proc_fops, surface_acpi->psu_registered); + proc_create_data("version", 0, surface_proc_dir, + &version_proc_fops, SURFACE_ACPI_VERSION); +} + +static void remove_surface_proc_entries(void) +{ + remove_proc_entry("BAT1", surface_proc_dir); + remove_proc_entry("BAT2", surface_proc_dir); + remove_proc_entry("ADP1", surface_proc_dir); + remove_proc_entry("version", surface_proc_dir); +} + +static void surface_acpi_notify(struct acpi_device *dev, u32 event) +{ + pr_info("surface_acpi: Event received %x\n", event); +} + +static void surface_acpi_register_rqst_handler(void) +{ + acpi_status status; + + status = acpi_get_handle(NULL, SUR_SAN_RQST, &surface_acpi->rqst_handle); + if (ACPI_FAILURE(status)) { + pr_err("surface_acpi: ACPI event failure status %s\n", + acpi_format_exception(status)); + } +} + +static void surface_acpi_register_rqsx_handler(void) +{ + acpi_status status; + + status = acpi_get_handle(NULL, SUR_SAN_RQSX, &surface_acpi->rqsx_handle); + if (ACPI_FAILURE(status)) { + pr_err("surface_acpi: ACPI event failure status %s\n", + acpi_format_exception(status)); + } +} + +static acpi_status surface_acpi_walk_callback(acpi_handle handle, u32 level, + void *context, void **return_value) +{ + struct acpi_device_info *info; + + if (ACPI_SUCCESS(acpi_get_object_info(handle, &info))) { + pr_warn("method: name: %4.4s, args %X\n", + (char *)&info->name, info->param_count); + + kfree(info); + } + + return AE_OK; +} + +static void surface_acpi_walk_namespace(struct acpi_device *dev) +{ + acpi_status status; + + status = acpi_walk_namespace(ACPI_TYPE_METHOD, + dev->handle, 1, surface_acpi_walk_callback, + NULL, NULL, NULL); + if (ACPI_FAILURE(status)) + pr_warn("surface_acpi: Unable to walk acpi resources\n"); +} + +static int surface_acpi_add(struct acpi_device *dev) +{ + if (!surface_acpi) + { + surface_acpi = kzalloc(sizeof(*surface_acpi), GFP_KERNEL); + if (!surface_acpi) + return AE_NO_MEMORY; + } + + if (acpi_has_method(dev->handle, SUR_METHOD_DSM)) + { + pr_info("surface_acpi: Attaching device MSHW0091\n"); + + surface_acpi->san_dev = dev; + surface_acpi->handle = dev->handle; + + surface_acpi_walk_namespace(surface_acpi->san_dev); + surface_acpi_check_status(surface_acpi->san_dev); + + surface_acpi_register_rqst_handler(); + surface_acpi_register_rqsx_handler(); + + surface_acpi_san_reg(); + surface_acpi_san_load(); + + create_surface_proc_entries(); + } + else if (acpi_has_method(dev->handle, SUR_METHOD_CRS)) + { + pr_info("surface_acpi: Attaching device MSHW0084\n"); + + surface_acpi->ssh_dev = dev; + + surface_acpi_walk_namespace(surface_acpi->ssh_dev); + surface_acpi_check_status(surface_acpi->ssh_dev); + + surface_acpi_ssh_initialize(); + //surface_acpi_ssh_load(); + } + else + { + pr_info("surface_acpi: Attaching device\n"); + } + + device_init_wakeup(&dev->dev, true); + + return AE_OK; +} + +static int surface_acpi_remove(struct acpi_device *dev) +{ + remove_surface_proc_entries(); + + return AE_OK; +} + +static const struct acpi_device_id surface_device_ids[] = { + {"MSHW0084", 0}, + {"MSHW0091", 0}, + {"MSHW0124", 0}, + {"INT3403", 0}, + {"LNXTHERM", 0}, + {"PNP0C0A", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, surface_device_ids); + +static struct acpi_driver surface_acpi_driver = { + .name = "surface_acpi", + .owner = THIS_MODULE, + .ids = surface_device_ids, + .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, + .ops = { + .add = surface_acpi_add, + .remove = surface_acpi_remove, + .notify = surface_acpi_notify, + }, +}; + +static int __init surface_acpi_init(void) +{ + int ret; + + pr_info("surface_acpi: Microsoft Surface ACPI Driver version %s\n", + SURFACE_ACPI_VERSION); + + surface_proc_dir = proc_mkdir(PROC_SURFACE, acpi_root_dir); + if (!surface_proc_dir) { + pr_err("surface_acpi: Unable to create proc dir " PROC_SURFACE "\n"); + return -ENODEV; + } + + ret = acpi_bus_register_driver(&surface_acpi_driver); + if (ret) { + pr_err("surface_acpi: Failed to register ACPI driver: %d\n", ret); + remove_proc_entry(PROC_SURFACE, acpi_root_dir); + } + + return ret; +} + +static void __exit surface_acpi_exit(void) +{ + acpi_bus_unregister_driver(&surface_acpi_driver); + if (surface_proc_dir) + remove_proc_entry(PROC_SURFACE, acpi_root_dir); +} + +module_init(surface_acpi_init); +module_exit(surface_acpi_exit); + +MODULE_AUTHOR("Jake Day"); +MODULE_DESCRIPTION("Microsoft Surface ACPI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/surface_acpi.h b/drivers/platform/x86/surface_acpi.h new file mode 100644 index 000000000000..5b6627c4d6f1 --- /dev/null +++ b/drivers/platform/x86/surface_acpi.h @@ -0,0 +1,18 @@ +/* + * surface_acpi.h - Microsoft Surface ACPI Driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + */ + +acpi_status surface_acpi_event_handler(u32 event); diff --git a/drivers/platform/x86/surface_i2c.c b/drivers/platform/x86/surface_i2c.c new file mode 100644 index 000000000000..fb2cf0cae72f --- /dev/null +++ b/drivers/platform/x86/surface_i2c.c @@ -0,0 +1,696 @@ +/* + * surface_i2c.c - Microsoft Surface I2C Driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "surface_acpi.h" + +#define POLL_INTERVAL (HZ * 2) + +struct surface_i2c_data { + struct i2c_client *adp1; + struct i2c_client *bat0; + unsigned short notify_version; + struct task_struct *poll_task; + bool kthread_running; + bool charging; + bool bat_charging; + u8 trip_point; + s32 full_capacity; +}; + +struct surface_i2c_lookup { + struct surface_i2c_data *cdata; + unsigned int n; + unsigned int index; + int addr; +}; + +struct surface_i2c_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 0x1 +#define ACPI_BATTERY_STATE_CHARGING 0x2 +#define ACPI_BATTERY_STATE_CRITICAL 0x4 + +#define surface_i2c_CMD_DEST_BAT0 0x01 +#define surface_i2c_CMD_DEST_ADP1 0x03 + +#define surface_i2c_CMD_BAT0_STA 0x01 +#define surface_i2c_CMD_BAT0_BIX 0x02 +#define surface_i2c_CMD_BAT0_BCT 0x03 +#define surface_i2c_CMD_BAT0_BTM 0x04 +#define surface_i2c_CMD_BAT0_BST 0x05 +#define surface_i2c_CMD_BAT0_BTP 0x06 +#define surface_i2c_CMD_ADP1_PSR 0x07 +#define surface_i2c_CMD_BAT0_PSOC 0x09 +#define surface_i2c_CMD_BAT0_PMAX 0x0A +#define surface_i2c_CMD_BAT0_PSRC 0x0B +#define surface_i2c_CMD_BAT0_CHGI 0x0C +#define surface_i2c_CMD_BAT0_ARTG 0x0D + +#define surface_i2c_NOTIFY_GET_VERSION 0x00 +#define surface_i2c_NOTIFY_ADP1 0x01 +#define surface_i2c_NOTIFY_BAT0_BST 0x02 +#define surface_i2c_NOTIFY_BAT0_BIX 0x05 + +#define surface_i2c_ADP1_REG_PSR 0x03 + +#define surface_i2c_BAT0_REG_CAPACITY 0x0c +#define surface_i2c_BAT0_REG_FULL_CHG_CAPACITY 0x0e +#define surface_i2c_BAT0_REG_DESIGN_CAPACITY 0x40 +#define surface_i2c_BAT0_REG_VOLTAGE 0x08 +#define surface_i2c_BAT0_REG_RATE 0x14 +#define surface_i2c_BAT0_REG_OEM 0x45 +#define surface_i2c_BAT0_REG_TYPE 0x4e +#define surface_i2c_BAT0_REG_SERIAL_NO 0x56 +#define surface_i2c_BAT0_REG_CYCLE_CNT 0x6e + +#define surface_i2c_EV_2_5 0x1ff + +static int surface_i2c_read_block(struct i2c_client *client, u8 reg, u8 *buf, + int len) +{ + int status, i; + + for (i = 0; i < len; i++) { + status = i2c_smbus_read_byte_data(client, reg + i); + if (status < 0) { + buf[i] = 0xff; + continue; + } + + buf[i] = (u8)status; + } + + return 0; +} + +static int +surface_i2c_notify(struct surface_i2c_data *cdata, u8 arg1, u8 arg2, + unsigned int *ret_value) +{ + /*static const guid_t surface_i2c_guid = + GUID_INIT(0x93b666c5, 0x70c6, 0x469f, + 0xa2, 0x15, 0x3d, 0x48, 0x7c, 0x91, 0xab, 0x3c);*/ + + struct acpi_device *adev; + acpi_handle handle; + acpi_status status; + + handle = ACPI_HANDLE(&cdata->adp1->dev); + if (!handle || acpi_bus_get_device(handle, &adev)) + return -ENODEV; + + *ret_value = 0; + + status = surface_acpi_event_handler(arg2); + if (ACPI_FAILURE(status)) { + pr_err("surface_i2c: ACPI event failure status %s\n", + acpi_format_exception(status)); + } + + return 0; +} + +static const struct bix default_bix = { + .revision = 0x00, + .power_unit = 0x00, + .design_capacity = 0x1734, + .last_full_charg_capacity = 0x1734, + .battery_technology = 0x01, + .design_voltage = 0x1d92, + .design_capacity_of_warning = 0xc8, + .design_capacity_of_low = 0xc8, + .battery_capacity_granularity_1 = 0x45, + .battery_capacity_granularity_2 = 0x11, + .cycle_count = 0x01, + .measurement_accuracy = 0x00015F90, + .max_sampling_time = 0x03E8, + .min_sampling_time = 0x03E8, + .max_average_interval = 0x03E8, + .min_average_interval = 0x03E8, + .model = "PNP0C0A", + .serial = "1234567890", + .type = "SDS-BAT", + .OEM = "MICROSOFT", +}; + +static int surface_i2c_bix(struct surface_i2c_data *cdata, struct bix *bix) +{ + struct i2c_client *client = cdata->bat0; + int ret; + char buf[10]; + + *bix = default_bix; + + /* get design capacity */ + ret = i2c_smbus_read_word_data(client, surface_i2c_BAT0_REG_DESIGN_CAPACITY); + if (ret < 0) { + dev_err(&client->dev, "Error reading design capacity: %d\n", ret); + return ret; + } + bix->design_capacity = le16_to_cpu(ret); + + /* get last full charge capacity */ + ret = i2c_smbus_read_word_data(client, surface_i2c_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 = le16_to_cpu(ret); + + /* get serial number */ + ret = surface_i2c_read_block(client, surface_i2c_BAT0_REG_SERIAL_NO, + buf, 10); + if (ret) { + dev_err(&client->dev, "Error reading serial no: %d\n", ret); + return ret; + } + memcpy(bix->serial, buf + 7, 3); + memcpy(bix->serial + 3, buf, 6); + bix->serial[9] = '\0'; + + /* get cycle count */ + ret = i2c_smbus_read_word_data(client, surface_i2c_BAT0_REG_CYCLE_CNT); + if (ret < 0) { + dev_err(&client->dev, "Error reading cycle count: %d\n", ret); + return ret; + } + bix->cycle_count = le16_to_cpu(ret); + + /* get OEM name */ + ret = surface_i2c_read_block(client, surface_i2c_BAT0_REG_OEM, buf, 4); + if (ret) { + dev_err(&client->dev, "Error reading cycle count: %d\n", ret); + return ret; + } + memcpy(bix->OEM, buf, 3); + bix->OEM[4] = '\0'; + + return 0; +} + +static int surface_i2c_bst(struct surface_i2c_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, surface_i2c_BAT0_REG_RATE); + if (rate < 0) + return rate; + + capacity = i2c_smbus_read_word_data(client, surface_i2c_BAT0_REG_CAPACITY); + if (capacity < 0) + return capacity; + + voltage = i2c_smbus_read_word_data(client, surface_i2c_BAT0_REG_VOLTAGE); + if (voltage < 0) + return voltage; + + tmp = le16_to_cpu(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 = le16_to_cpu(capacity); + bst->battery_present_voltage = le16_to_cpu(voltage); + + return 0; +} + +static int surface_i2c_adp_psr(struct surface_i2c_data *cdata) +{ + struct i2c_client *client = cdata->adp1; + int ret; + + ret = i2c_smbus_read_byte_data(client, surface_i2c_ADP1_REG_PSR); + if (ret < 0) + return ret; + + return ret; +} + +static int surface_i2c_isr(struct surface_i2c_data *cdata) +{ + struct bst bst; + struct bix bix; + int ret; + bool status, bat_status; + + ret = surface_i2c_adp_psr(cdata); + if (ret < 0) + return ret; + + status = ret; + + if (status != cdata->charging) + surface_i2c_notify(cdata, cdata->notify_version, + surface_i2c_NOTIFY_ADP1, &ret); + + cdata->charging = status; + + ret = surface_i2c_bst(cdata, &bst); + if (ret < 0) + return ret; + + bat_status = bst.battery_state; + + if (bat_status != cdata->bat_charging) + surface_i2c_notify(cdata, cdata->notify_version, + surface_i2c_NOTIFY_BAT0_BST, &ret); + + cdata->bat_charging = bat_status; + + ret = surface_i2c_bix(cdata, &bix); + if (ret < 0) + return ret; + if (bix.last_full_charg_capacity != cdata->full_capacity) + surface_i2c_notify(cdata, cdata->notify_version, + surface_i2c_NOTIFY_BAT0_BIX, &ret); + + cdata->full_capacity = bix.last_full_charg_capacity; + + return 0; +} + +static int surface_i2c_poll_task(void *data) +{ + struct surface_i2c_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 = surface_i2c_isr(data); + if (ret) + goto out; + } + +out: + cdata->kthread_running = false; + return ret; +} + +static acpi_status +surface_i2c_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 surface_i2c_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 surface_i2c_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 == surface_i2c_CMD_DEST_ADP1 && + gsb->cmd.arg1 == surface_i2c_CMD_ADP1_PSR) { + ret = surface_i2c_adp_psr(cdata); + if (ret >= 0) { + status = ret; + ret = 0; + } + goto out; + } + + if (gsb->cmd.arg0 != surface_i2c_CMD_DEST_BAT0) { + ret = AE_BAD_PARAMETER; + goto err; + } + + switch (gsb->cmd.arg1) { + case surface_i2c_CMD_BAT0_STA: + status = 1; + ret = 0; + break; + case surface_i2c_CMD_BAT0_BIX: + status = 1; + ret = surface_i2c_bix(cdata, &gsb->bix); + break; + case surface_i2c_CMD_BAT0_BTP: + status = 1; + ret = 0; + cdata->trip_point = gsb->cmd.arg2; + break; + case surface_i2c_CMD_BAT0_BST: + status = 1; + ret = surface_i2c_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 surface_i2c_install_space_handler(struct i2c_client *client) +{ + acpi_handle handle; + struct surface_i2c_handler_data *data; + acpi_status status; + + handle = ACPI_HANDLE(&client->dev); + + if (!handle) + return -ENODEV; + + data = kzalloc(sizeof(struct surface_i2c_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, + &surface_i2c_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 surface_i2c_remove_space_handler(struct i2c_client *client) +{ + acpi_handle handle; + struct surface_i2c_handler_data *data; + acpi_status status; + + handle = ACPI_HANDLE(&client->dev); + + if (!handle) + return; + + acpi_remove_address_space_handler(handle, + ACPI_ADR_SPACE_GSBUS, + &surface_i2c_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 acpi_find_i2c(struct acpi_resource *ares, void *data) +{ + struct surface_i2c_lookup *lookup = data; + + if (ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) + return 1; + + if (lookup->n++ == lookup->index && !lookup->addr) + lookup->addr = ares->data.i2c_serial_bus.slave_address; + + return 1; +} + +static int surface_i2c_resource_lookup(struct surface_i2c_data *cdata, + unsigned int index) +{ + struct i2c_client *client = cdata->adp1; + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + struct surface_i2c_lookup lookup = { + .cdata = cdata, + .index = index, + }; + struct list_head res_list; + int ret; + + INIT_LIST_HEAD(&res_list); + + ret = acpi_dev_get_resources(adev, &res_list, acpi_find_i2c, &lookup); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&res_list); + + if (!lookup.addr) + return -ENOENT; + + return lookup.addr; +} + +static void surface_i2c_dump_registers(struct i2c_client *client, + struct i2c_client *bat0) +{ + char rd_buf[60]; + int error, i, c; + char buff[17 * 3 * 2] = {0}; + + dev_info(&client->dev, "dumping registers 0x00 to 0x7F:\n"); + + for (i = 0; i < 0x80; i += 0x20) { + memset(rd_buf, 0, sizeof(rd_buf)); + error = surface_i2c_read_block(bat0, i, rd_buf, 0x20); + dev_info(&client->dev, " read 0x%02x: %*ph|%*ph\n", + i, + 0x10, rd_buf, + 0x10, rd_buf + 0x10); + for (c = 0; c < 0x20; c++) { + if (rd_buf[c] >= 0x20 && rd_buf[c] <= 0x7e) { + buff[c * 3 + 0] = ' '; + buff[c * 3 + 1] = rd_buf[c]; + } else { + buff[c * 3 + 0] = '-'; + buff[c * 3 + 1] = '-'; + } + buff[c * 3 + 2] = (c + 1) % 0x10 ? ' ' : '|'; + } + buff[0x1f * 3 + 2] = '\0'; + dev_info(&client->dev, "ascii 0x%02x: %s\n", i, buff); + } +} + +static int surface_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct i2c_client *bat0; + struct surface_i2c_data *data; + int error, version, addr; + + pr_info("surface_i2c: Probing for surface i2c device...\n"); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->adp1 = client; + i2c_set_clientdata(client, data); + + addr = surface_i2c_resource_lookup(data, 1); + if (addr < 0) + return addr; + + bat0 = i2c_new_dummy(client->adapter, addr); + if (!bat0) + return -ENOMEM; + + data->bat0 = bat0; + i2c_set_clientdata(bat0, data); + + // debugging + surface_i2c_dump_registers(client, bat0); + + pr_info("surface_i2c: Attaching device MSHW0124..."); + + error = surface_i2c_notify(data, 1, surface_i2c_NOTIFY_GET_VERSION, &version); + if (error) + goto out_err; + + data->notify_version = version == surface_i2c_EV_2_5; + + data->poll_task = kthread_run(surface_i2c_poll_task, data, "surface_i2c_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 = surface_i2c_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 surface_i2c_remove(struct i2c_client *client) +{ + struct surface_i2c_data *cdata = i2c_get_clientdata(client); + + surface_i2c_remove_space_handler(client); + + if (cdata->kthread_running) + kthread_stop(cdata->poll_task); + + i2c_unregister_device(cdata->bat0); + + return 0; +} + +int surface_i2c_detect(struct i2c_client* client, struct i2c_board_info* board_info) +{ + pr_info("surface_i2c: Detecting surface_i2c device..."); + return 0; +} + +static const struct acpi_device_id surface_i2c_acpi_match[] = { + { "MSHW0124", 0 }, + { "", 0 } +}; +MODULE_DEVICE_TABLE(acpi, surface_i2c_acpi_match); + +static const struct i2c_device_id surface_i2c_id[] = { + { "MSHW0124:00", 0 }, + { "MSHW0124:01", 0 }, + { "", 0 } +}; +MODULE_DEVICE_TABLE(i2c, surface_i2c_id); + +static struct i2c_driver surface_i2c_driver = { + .probe_new = surface_i2c_probe, + .remove = surface_i2c_remove, + .detect = surface_i2c_detect, + .id_table = surface_i2c_id, + .driver = { + .name = "surface_i2c", + .acpi_match_table = ACPI_PTR(surface_i2c_acpi_match), + }, +}; +module_i2c_driver(surface_i2c_driver); + +MODULE_AUTHOR("Jake Day"); +MODULE_DESCRIPTION("Microsoft Surface I2C Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/surface_platform.c b/drivers/platform/x86/surface_platform.c new file mode 100644 index 000000000000..7a84340b04bb --- /dev/null +++ b/drivers/platform/x86/surface_platform.c @@ -0,0 +1,67 @@ +/* + * surface_platform.c - Microsoft Surface Platform Driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct surface_platform_data { + struct device *dev; +}; + +static int surface_platform_probe(struct platform_device *pdev) +{ + struct surface_platform_data *pdata; + + platform_set_drvdata(pdev, pdata); + return 0; +} + +static int surface_platform_remove(struct platform_device *pdev) +{ + struct surface_platform_data *pdata = platform_get_drvdata(pdev); +} + +static const struct acpi_device_id surface_platform_acpi_match[] = { + { "MSHW0091", 0 }, + { "INT3403", 0 }, + { "", 0 } +}; +MODULE_DEVICE_TABLE(acpi, surface_platform_acpi_match); + +static struct platform_driver surface_platform_driver = { + .probe = surface_platform_probe, + .remove = surface_platform_remove, + .driver = { + .name = "surface_platform", + .acpi_match_table = ACPI_PTR(surface_platform_acpi_match), + }, +}; +module_platform_driver(surface_platform_driver); + +MODULE_AUTHOR("Jake Day"); +MODULE_DESCRIPTION("Microsoft Surface Platform Driver"); +MODULE_LICENSE("GPL");