|  | // 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 <benjamin.tissoires@gmail.com> | 
|  | * (C) Copyright 2016 Stephen Just <stephenjust@gmail.com> | 
|  | * | 
|  | * 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 <linux/acpi.h> | 
|  | #include <linux/bits.h> | 
|  | #include <linux/freezer.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/kthread.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/types.h> | 
|  | #include <linux/uuid.h> | 
|  | #include <asm/unaligned.h> | 
|  |  | 
|  | #define SURFACE_3_POLL_INTERVAL		(2 * HZ) | 
|  | #define SURFACE_3_STRLEN		10 | 
|  |  | 
|  | 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_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[SURFACE_3_STRLEN]; | 
|  | char	serial[SURFACE_3_STRLEN]; | 
|  | char	type[SURFACE_3_STRLEN]; | 
|  | char	OEM[SURFACE_3_STRLEN]; | 
|  | } __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_MASK		GENMASK(8, 0) | 
|  |  | 
|  | /* 3f99e367-6220-4955-8b0f-06ef2ae79412 */ | 
|  | static const guid_t mshw0011_guid = | 
|  | GUID_INIT(0x3F99E367, 0x6220, 0x4955, 0x8B, 0x0F, 0x06, 0xEF, | 
|  | 0x2A, 0xE7, 0x94, 0x12); | 
|  |  | 
|  | static int | 
|  | mshw0011_notify(struct mshw0011_data *cdata, u8 arg1, u8 arg2, | 
|  | unsigned int *ret_value) | 
|  | { | 
|  | union acpi_object *obj; | 
|  | acpi_handle handle; | 
|  | unsigned int i; | 
|  |  | 
|  | handle = ACPI_HANDLE(&cdata->adp1->dev); | 
|  | if (!handle) | 
|  | 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[SURFACE_3_STRLEN]; | 
|  | 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, on some devices (with unofficial replacement | 
|  | * battery?) reading any of the serial number range addresses gets | 
|  | * nacked in this case just leave the serial number empty. | 
|  | */ | 
|  | ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_SERIAL_NO, | 
|  | sizeof(buf), buf); | 
|  | if (ret == -EREMOTEIO) { | 
|  | /* no serial number available */ | 
|  | } else if (ret != sizeof(buf)) { | 
|  | dev_err(&client->dev, "Error reading serial no: %d\n", ret); | 
|  | return ret; | 
|  | } else { | 
|  | snprintf(bix->serial, ARRAY_SIZE(bix->serial), "%3pE%6pE", buf + 7, 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), "%3pE", 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) | 
|  | { | 
|  | return i2c_smbus_read_byte_data(cdata->adp1, MSHW0011_ADP1_REG_PSR); | 
|  | } | 
|  |  | 
|  | 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(SURFACE_3_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 || !i2c_acpi_get_i2c_resource(ares, &sb)) { | 
|  | 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) { | 
|  | status = mshw0011_adp_psr(cdata); | 
|  | if (status >= 0) { | 
|  | ret = AE_OK; | 
|  | goto out; | 
|  | } else { | 
|  | ret = AE_ERROR; | 
|  | goto err; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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: | 
|  | dev_info(&cdata->bat0->dev, "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) | 
|  | { | 
|  | struct acpi_device *adev; | 
|  | struct mshw0011_handler_data *data; | 
|  | acpi_status status; | 
|  |  | 
|  | adev = ACPI_COMPANION(&client->dev); | 
|  | if (!adev) | 
|  | return -ENODEV; | 
|  |  | 
|  | data = kzalloc(sizeof(struct mshw0011_handler_data), | 
|  | GFP_KERNEL); | 
|  | if (!data) | 
|  | return -ENOMEM; | 
|  |  | 
|  | data->client = client; | 
|  | status = acpi_bus_attach_private_data(adev->handle, (void *)data); | 
|  | if (ACPI_FAILURE(status)) { | 
|  | kfree(data); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | status = acpi_install_address_space_handler(adev->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(adev->handle); | 
|  | kfree(data); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | acpi_dev_clear_dependencies(adev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void mshw0011_remove_space_handler(struct i2c_client *client) | 
|  | { | 
|  | struct mshw0011_handler_data *data; | 
|  | acpi_handle handle; | 
|  | acpi_status status; | 
|  |  | 
|  | handle = ACPI_HANDLE(&client->dev); | 
|  | 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)); | 
|  | strscpy(board_info.type, "MSHW0011-bat0", I2C_NAME_SIZE); | 
|  |  | 
|  | bat0 = i2c_acpi_new_device(dev, 1, &board_info); | 
|  | if (IS_ERR(bat0)) | 
|  | return PTR_ERR(bat0); | 
|  |  | 
|  | 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_MASK; | 
|  |  | 
|  | 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 void 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); | 
|  | } | 
|  |  | 
|  | 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 = mshw0011_probe, | 
|  | .remove = mshw0011_remove, | 
|  | .driver = { | 
|  | .name = "mshw0011", | 
|  | .acpi_match_table = mshw0011_acpi_match, | 
|  | }, | 
|  | }; | 
|  | module_i2c_driver(mshw0011_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); | 
|  | MODULE_DESCRIPTION("mshw0011 driver"); | 
|  | MODULE_LICENSE("GPL v2"); |