// 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 3 f99e367 - 6220 - 4955 - 8 b0f - 06 ef2ae79412 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 :
* . 0 x22 seems to control the ADP1 line status ( and probably the charger )
* . 0 x55 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 0 x00 . . 0 x7F
* . if bit 8 ( 0 x80 ) 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 0 x01
#define MSHW0011_CMD_DEST_ADP1 0 x03
#define MSHW0011_CMD_BAT0_STA 0 x01
#define MSHW0011_CMD_BAT0_BIX 0 x02
#define MSHW0011_CMD_BAT0_BCT 0 x03
#define MSHW0011_CMD_BAT0_BTM 0 x04
#define MSHW0011_CMD_BAT0_BST 0 x05
#define MSHW0011_CMD_BAT0_BTP 0 x06
#define MSHW0011_CMD_ADP1_PSR 0 x07
#define MSHW0011_CMD_BAT0_PSOC 0 x09
#define MSHW0011_CMD_BAT0_PMAX 0 x0a
#define MSHW0011_CMD_BAT0_PSRC 0 x0b
#define MSHW0011_CMD_BAT0_CHGI 0 x0c
#define MSHW0011_CMD_BAT0_ARTG 0 x0d
#define MSHW0011_NOTIFY_GET_VERSION 0 x00
#define MSHW0011_NOTIFY_ADP1 0 x01
#define MSHW0011_NOTIFY_BAT0_BST 0 x02
#define MSHW0011_NOTIFY_BAT0_BIX 0 x05
#define MSHW0011_ADP1_REG_PSR 0 x04
#define MSHW0011_BAT0_REG_CAPACITY 0 x0c
#define MSHW0011_BAT0_REG_FULL_CHG_CAPACITY 0 x0e
#define MSHW0011_BAT0_REG_DESIGN_CAPACITY 0 x40
#define MSHW0011_BAT0_REG_VOLTAGE 0 x08
#define MSHW0011_BAT0_REG_RATE 0 x14
#define MSHW0011_BAT0_REG_OEM 0 x45
#define MSHW0011_BAT0_REG_TYPE 0 x4e
#define MSHW0011_BAT0_REG_SERIAL_NO 0 x56
#define MSHW0011_BAT0_REG_CYCLE_CNT 0 x6e
#define MSHW0011_EV_2_5_MASK GENMASK(8 , 0 )
/* 3f99e367-6220-4955-8b0f-06ef2ae79412 */
static const guid_t mshw0011_guid =
GUID_INIT(0 x3F99E367, 0 x6220, 0 x4955, 0 x8B, 0 x0F, 0 x06, 0 xEF,
0 x2A, 0 xE7, 0 x94, 0 x12);
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 = 0 x00,
.power_unit = 0 x01,
.design_capacity = 0 x1dca,
.last_full_charg_capacity = 0 x1dca,
.battery_technology = 0 x01,
.design_voltage = 0 x10df,
.design_capacity_of_warning = 0 x8f,
.design_capacity_of_low = 0 x47,
.cycle_count = 0 xffffffff,
.measurement_accuracy = 0 x00015f90,
.max_sampling_time = 0 x03e8,
.min_sampling_time = 0 x03e8,
.max_average_interval = 0 x03e8,
.min_average_interval = 0 x03e8,
.battery_capacity_granularity_1 = 0 x45,
.battery_capacity_granularity_2 = 0 x11,
.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" );
Messung V0.5 in Prozent C=93 H=95 G=93
¤ Dauer der Verarbeitung: 0.8 Sekunden
¤
*© Formatika GbR, Deutschland