root/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 <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 <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_obj(struct mshw0011_handler_data);
        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");