linux/drivers/mfd/acer-ec-a500.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Acer Iconia Tab A500 Embedded Controller Driver
   4 *
   5 * Copyright 2020 GRATE-driver project
   6 */
   7
   8#include <linux/delay.h>
   9#include <linux/i2c.h>
  10#include <linux/mfd/core.h>
  11#include <linux/module.h>
  12#include <linux/of_device.h>
  13#include <linux/reboot.h>
  14#include <linux/regmap.h>
  15
  16#define A500_EC_I2C_ERR_TIMEOUT         500
  17#define A500_EC_POWER_CMD_TIMEOUT       1000
  18
  19/*
  20 * Controller's firmware expects specific command opcodes to be used for the
  21 * corresponding registers. Unsupported commands are skipped by the firmware.
  22 */
  23#define CMD_SHUTDOWN                    0x0
  24#define CMD_WARM_REBOOT                 0x0
  25#define CMD_COLD_REBOOT                 0x1
  26
  27enum {
  28        REG_CURRENT_NOW = 0x03,
  29        REG_SHUTDOWN = 0x52,
  30        REG_WARM_REBOOT = 0x54,
  31        REG_COLD_REBOOT = 0x55,
  32};
  33
  34static struct i2c_client *a500_ec_client_pm_off;
  35
  36static int a500_ec_read(void *context, const void *reg_buf, size_t reg_size,
  37                        void *val_buf, size_t val_sizel)
  38{
  39        struct i2c_client *client = context;
  40        unsigned int reg, retries = 5;
  41        u16 *ret_val = val_buf;
  42        s32 ret = 0;
  43
  44        reg = *(u8 *)reg_buf;
  45
  46        while (retries-- > 0) {
  47                ret = i2c_smbus_read_word_data(client, reg);
  48                if (ret >= 0)
  49                        break;
  50
  51                msleep(A500_EC_I2C_ERR_TIMEOUT);
  52        }
  53
  54        if (ret < 0) {
  55                dev_err(&client->dev, "read 0x%x failed: %d\n", reg, ret);
  56                return ret;
  57        }
  58
  59        *ret_val = ret;
  60
  61        if (reg == REG_CURRENT_NOW)
  62                fsleep(10000);
  63
  64        return 0;
  65}
  66
  67static int a500_ec_write(void *context, const void *data, size_t count)
  68{
  69        struct i2c_client *client = context;
  70        unsigned int reg, val, retries = 5;
  71        s32 ret = 0;
  72
  73        reg = *(u8  *)(data + 0);
  74        val = *(u16 *)(data + 1);
  75
  76        while (retries-- > 0) {
  77                ret = i2c_smbus_write_word_data(client, reg, val);
  78                if (ret >= 0)
  79                        break;
  80
  81                msleep(A500_EC_I2C_ERR_TIMEOUT);
  82        }
  83
  84        if (ret < 0) {
  85                dev_err(&client->dev, "write 0x%x failed: %d\n", reg, ret);
  86                return ret;
  87        }
  88
  89        return 0;
  90}
  91
  92static const struct regmap_config a500_ec_regmap_config = {
  93        .name = "KB930",
  94        .reg_bits = 8,
  95        .val_bits = 16,
  96        .max_register = 0xff,
  97};
  98
  99static const struct regmap_bus a500_ec_regmap_bus = {
 100        .reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
 101        .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
 102        .write = a500_ec_write,
 103        .read = a500_ec_read,
 104        .max_raw_read = 2,
 105};
 106
 107static void a500_ec_poweroff(void)
 108{
 109        i2c_smbus_write_word_data(a500_ec_client_pm_off,
 110                                  REG_SHUTDOWN, CMD_SHUTDOWN);
 111
 112        mdelay(A500_EC_POWER_CMD_TIMEOUT);
 113}
 114
 115static int a500_ec_restart_notify(struct notifier_block *this,
 116                                  unsigned long reboot_mode, void *data)
 117{
 118        if (reboot_mode == REBOOT_WARM)
 119                i2c_smbus_write_word_data(a500_ec_client_pm_off,
 120                                          REG_WARM_REBOOT, CMD_WARM_REBOOT);
 121        else
 122                i2c_smbus_write_word_data(a500_ec_client_pm_off,
 123                                          REG_COLD_REBOOT, CMD_COLD_REBOOT);
 124
 125        mdelay(A500_EC_POWER_CMD_TIMEOUT);
 126
 127        return NOTIFY_DONE;
 128}
 129
 130static struct notifier_block a500_ec_restart_handler = {
 131        .notifier_call = a500_ec_restart_notify,
 132        .priority = 200,
 133};
 134
 135static const struct mfd_cell a500_ec_cells[] = {
 136        { .name = "acer-a500-iconia-battery", },
 137        { .name = "acer-a500-iconia-leds", },
 138};
 139
 140static int a500_ec_probe(struct i2c_client *client)
 141{
 142        struct regmap *regmap;
 143        int err;
 144
 145        regmap = devm_regmap_init(&client->dev, &a500_ec_regmap_bus,
 146                                  client, &a500_ec_regmap_config);
 147        if (IS_ERR(regmap))
 148                return PTR_ERR(regmap);
 149
 150        err = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
 151                                   a500_ec_cells, ARRAY_SIZE(a500_ec_cells),
 152                                   NULL, 0, NULL);
 153        if (err) {
 154                dev_err(&client->dev, "failed to add sub-devices: %d\n", err);
 155                return err;
 156        }
 157
 158        if (of_device_is_system_power_controller(client->dev.of_node)) {
 159                a500_ec_client_pm_off = client;
 160
 161                err = register_restart_handler(&a500_ec_restart_handler);
 162                if (err)
 163                        return err;
 164
 165                if (!pm_power_off)
 166                        pm_power_off = a500_ec_poweroff;
 167        }
 168
 169        return 0;
 170}
 171
 172static int a500_ec_remove(struct i2c_client *client)
 173{
 174        if (of_device_is_system_power_controller(client->dev.of_node)) {
 175                if (pm_power_off == a500_ec_poweroff)
 176                        pm_power_off = NULL;
 177
 178                unregister_restart_handler(&a500_ec_restart_handler);
 179        }
 180
 181        return 0;
 182}
 183
 184static const struct of_device_id a500_ec_match[] = {
 185        { .compatible = "acer,a500-iconia-ec" },
 186        { }
 187};
 188MODULE_DEVICE_TABLE(of, a500_ec_match);
 189
 190static struct i2c_driver a500_ec_driver = {
 191        .driver = {
 192                .name = "acer-a500-embedded-controller",
 193                .of_match_table = a500_ec_match,
 194        },
 195        .probe_new = a500_ec_probe,
 196        .remove = a500_ec_remove,
 197};
 198module_i2c_driver(a500_ec_driver);
 199
 200MODULE_DESCRIPTION("Acer Iconia Tab A500 Embedded Controller driver");
 201MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
 202MODULE_LICENSE("GPL");
 203