linux/drivers/platform/surface/surface3_power.c

// 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 <[email protected]>
 * (C) Copyright 2016 Stephen Just <[email protected]>
 *
 * 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 <linux/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 <[email protected]>");
MODULE_DESCRIPTION("mshw0011 driver");
MODULE_LICENSE("GPL v2");