cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

acer-ec-a500.c (4539B)


      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");