mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-11-01 17:08:10 +00:00
chrome platform changes for 6.11
* New - Add "cros_ec_hwmon" driver to expose fan speed and temperature. - Add "cros_charge-control" driver to control charge thresholds and behaviour. - Add module parameter "log_poll_period_ms" in cros_ec_debugfs for tuning the poll period. - Support version 3 of EC_CMD_GET_NEXT_EVENT and keyboard matrix. * Fixes - Fix a race condition in accessing MEC (Microchip EC) memory between ACPI and kernel. Serialize the memory access by an AML (ACPI Machine Language) mutex. - Fix an issue of wrong EC message version in cros_ec_debugfs. * Misc - Fix kernel-doc errors and cleanups. -----BEGIN PGP SIGNATURE----- iIkEABYKADEWIQS0yQeDP3cjLyifNRUrxTEGBto89AUCZo04zBMcdHp1bmdiaUBr ZXJuZWwub3JnAAoJECvFMQYG2jz0bf0BAOjE7APATFsKLuFmtKxk/1XlsspsRTWK vocdNAKuj9I5AQCdcrHcDreLz5ldqodCpOc4TXiZoLHIkELNQcQtOorhDw== =To8o -----END PGP SIGNATURE----- Merge tag 'tag-chrome-platform-for-v6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux Pull chrome platform updates from Tzung-Bi Shih: "New code: - Add "cros_ec_hwmon" driver to expose fan speed and temperature - Add "cros_charge-control" driver to control charge thresholds and behaviour - Add module parameter "log_poll_period_ms" in cros_ec_debugfs for tuning the poll period - Support version 3 of EC_CMD_GET_NEXT_EVENT and keyboard matrix Fixes: - Fix a race condition in accessing MEC (Microchip EC) memory between ACPI and kernel. Serialize the memory access by an AML (ACPI Machine Language) mutex - Fix an issue of wrong EC message version in cros_ec_debugfs Misc: - Fix kernel-doc errors and cleanups" * tag 'tag-chrome-platform-for-v6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux: (28 commits) power: supply: cros_charge-control: Fix signedness bug in charge_behaviour_store() power: supply: cros_charge-control: Avoid accessing attributes out of bounds power: supply: cros_charge-control: don't load if Framework control is present power: supply: add ChromeOS EC based charge control driver platform/chrome: cros_ec_proto: Introduce cros_ec_get_cmd_versions() platform/chrome: Update binary interface for EC-based charge control ACPI: battery: add devm_battery_hook_register() dt-bindings: input: cros-ec-keyboard: Add keyboard matrix v3.0 platform/chrome: cros_ec_lpc: Handle zero length read/write platform/chrome: cros_ec_lpc: Fix error code in cros_ec_lpc_mec_read_bytes() platform/chrome: cros_ec_debugfs: fix wrong EC message version platform/chrome: cros_ec_proto: update Kunit test for get_next_data_v3 platform/chrome: cros_ec_proto: add missing MODULE_DESCRIPTION() macro hwmon: (cros_ec) Fix access to restricted __le16 hwmon: (cros_ec) Prevent read overflow in probe() platform/chrome: cros_ec_lpc: Add quirks for Framework Laptop platform/chrome: cros_ec_lpc: Add a new quirk for AML mutex platform/chrome: cros_ec_lpc: Add a new quirk for ACPI id platform/chrome: cros_ec_lpc: MEC access can use an AML mutex platform/chrome: cros_ec_lpc: MEC access can return error code ...
This commit is contained in:
commit
89c4913893
22 changed files with 1270 additions and 99 deletions
26
Documentation/hwmon/cros_ec_hwmon.rst
Normal file
26
Documentation/hwmon/cros_ec_hwmon.rst
Normal file
|
@ -0,0 +1,26 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
Kernel driver cros_ec_hwmon
|
||||
===========================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* ChromeOS embedded controllers.
|
||||
|
||||
Prefix: 'cros_ec'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Author:
|
||||
|
||||
- Thomas Weißschuh <linux@weissschuh.net>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for hardware monitoring commands exposed by the
|
||||
ChromeOS embedded controller used in Chromebooks and other devices.
|
||||
|
||||
The channel labels exposed via hwmon are retrieved from the EC itself.
|
||||
|
||||
Fan and temperature readings are supported.
|
|
@ -58,6 +58,7 @@ Hardware Monitoring Kernel Drivers
|
|||
coretemp
|
||||
corsair-cpro
|
||||
corsair-psu
|
||||
cros_ec_hwmon
|
||||
da9052
|
||||
da9055
|
||||
dell-smm-hwmon
|
||||
|
|
14
MAINTAINERS
14
MAINTAINERS
|
@ -5128,11 +5128,25 @@ S: Maintained
|
|||
F: Documentation/devicetree/bindings/sound/google,cros-ec-codec.yaml
|
||||
F: sound/soc/codecs/cros_ec_codec.*
|
||||
|
||||
CHROMEOS EC CHARGE CONTROL
|
||||
M: Thomas Weißschuh <thomas@weissschuh.net>
|
||||
S: Maintained
|
||||
F: drivers/power/supply/cros_charge-control.c
|
||||
|
||||
CHROMEOS EC HARDWARE MONITORING
|
||||
M: Thomas Weißschuh <thomas@weissschuh.net>
|
||||
L: chrome-platform@lists.linux.dev
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/cros_ec_hwmon.rst
|
||||
F: drivers/hwmon/cros_ec_hwmon.c
|
||||
|
||||
CHROMEOS EC SUBDRIVERS
|
||||
M: Benson Leung <bleung@chromium.org>
|
||||
R: Guenter Roeck <groeck@chromium.org>
|
||||
L: chrome-platform@lists.linux.dev
|
||||
S: Maintained
|
||||
F: drivers/power/supply/cros_charge-control.c
|
||||
F: drivers/power/supply/cros_usbpd-charger.c
|
||||
N: cros_ec
|
||||
N: cros-ec
|
||||
|
|
|
@ -756,6 +756,21 @@ void battery_hook_register(struct acpi_battery_hook *hook)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(battery_hook_register);
|
||||
|
||||
static void devm_battery_hook_unregister(void *data)
|
||||
{
|
||||
struct acpi_battery_hook *hook = data;
|
||||
|
||||
battery_hook_unregister(hook);
|
||||
}
|
||||
|
||||
int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
|
||||
{
|
||||
battery_hook_register(hook);
|
||||
|
||||
return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_battery_hook_register);
|
||||
|
||||
/*
|
||||
* This function gets called right after the battery sysfs
|
||||
* attributes have been added, so that the drivers that
|
||||
|
|
|
@ -506,6 +506,17 @@ config SENSORS_CORSAIR_PSU
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called corsair-psu.
|
||||
|
||||
config SENSORS_CROS_EC
|
||||
tristate "ChromeOS Embedded Controller sensors"
|
||||
depends on MFD_CROS_EC_DEV
|
||||
default MFD_CROS_EC_DEV
|
||||
help
|
||||
If you say yes here you get support for ChromeOS Embedded Controller
|
||||
sensors.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called cros_ec_hwmon.
|
||||
|
||||
config SENSORS_DRIVETEMP
|
||||
tristate "Hard disk drives with temperature sensors"
|
||||
depends on SCSI && ATA
|
||||
|
|
|
@ -64,6 +64,7 @@ obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
|
|||
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
|
||||
obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o
|
||||
obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o
|
||||
obj-$(CONFIG_SENSORS_CROS_EC) += cros_ec_hwmon.o
|
||||
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o
|
||||
|
|
283
drivers/hwmon/cros_ec_hwmon.c
Normal file
283
drivers/hwmon/cros_ec_hwmon.c
Normal file
|
@ -0,0 +1,283 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* ChromeOS EC driver for hwmon
|
||||
*
|
||||
* Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/cros_ec_commands.h>
|
||||
#include <linux/platform_data/cros_ec_proto.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#define DRV_NAME "cros-ec-hwmon"
|
||||
|
||||
struct cros_ec_hwmon_priv {
|
||||
struct cros_ec_device *cros_ec;
|
||||
const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
|
||||
u8 usable_fans;
|
||||
};
|
||||
|
||||
static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
|
||||
{
|
||||
int ret;
|
||||
__le16 __speed;
|
||||
|
||||
ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_FAN + index * 2, 2, &__speed);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*speed = le16_to_cpu(__speed);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
|
||||
{
|
||||
unsigned int offset;
|
||||
int ret;
|
||||
|
||||
if (index < EC_TEMP_SENSOR_ENTRIES)
|
||||
offset = EC_MEMMAP_TEMP_SENSOR + index;
|
||||
else
|
||||
offset = EC_MEMMAP_TEMP_SENSOR_B + index - EC_TEMP_SENSOR_ENTRIES;
|
||||
|
||||
ret = cros_ec_cmd_readmem(cros_ec, offset, 1, temp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool cros_ec_hwmon_is_error_fan(u16 speed)
|
||||
{
|
||||
return speed == EC_FAN_SPEED_NOT_PRESENT || speed == EC_FAN_SPEED_STALLED;
|
||||
}
|
||||
|
||||
static bool cros_ec_hwmon_is_error_temp(u8 temp)
|
||||
{
|
||||
return temp == EC_TEMP_SENSOR_NOT_PRESENT ||
|
||||
temp == EC_TEMP_SENSOR_ERROR ||
|
||||
temp == EC_TEMP_SENSOR_NOT_POWERED ||
|
||||
temp == EC_TEMP_SENSOR_NOT_CALIBRATED;
|
||||
}
|
||||
|
||||
static long cros_ec_hwmon_temp_to_millicelsius(u8 temp)
|
||||
{
|
||||
return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET));
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
|
||||
int ret = -EOPNOTSUPP;
|
||||
u16 speed;
|
||||
u8 temp;
|
||||
|
||||
if (type == hwmon_fan) {
|
||||
if (attr == hwmon_fan_input) {
|
||||
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
|
||||
if (ret == 0) {
|
||||
if (cros_ec_hwmon_is_error_fan(speed))
|
||||
ret = -ENODATA;
|
||||
else
|
||||
*val = speed;
|
||||
}
|
||||
} else if (attr == hwmon_fan_fault) {
|
||||
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
|
||||
if (ret == 0)
|
||||
*val = cros_ec_hwmon_is_error_fan(speed);
|
||||
}
|
||||
} else if (type == hwmon_temp) {
|
||||
if (attr == hwmon_temp_input) {
|
||||
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
|
||||
if (ret == 0) {
|
||||
if (cros_ec_hwmon_is_error_temp(temp))
|
||||
ret = -ENODATA;
|
||||
else
|
||||
*val = cros_ec_hwmon_temp_to_millicelsius(temp);
|
||||
}
|
||||
} else if (attr == hwmon_temp_fault) {
|
||||
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
|
||||
if (ret == 0)
|
||||
*val = cros_ec_hwmon_is_error_temp(temp);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (type == hwmon_temp && attr == hwmon_temp_label) {
|
||||
*str = priv->temp_sensor_names[channel];
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct cros_ec_hwmon_priv *priv = data;
|
||||
|
||||
if (type == hwmon_fan) {
|
||||
if (priv->usable_fans & BIT(channel))
|
||||
return 0444;
|
||||
} else if (type == hwmon_temp) {
|
||||
if (priv->temp_sensor_names[channel])
|
||||
return 0444;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT,
|
||||
HWMON_F_INPUT | HWMON_F_FAULT),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops cros_ec_hwmon_ops = {
|
||||
.read = cros_ec_hwmon_read,
|
||||
.read_string = cros_ec_hwmon_read_string,
|
||||
.is_visible = cros_ec_hwmon_is_visible,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info cros_ec_hwmon_chip_info = {
|
||||
.ops = &cros_ec_hwmon_ops,
|
||||
.info = cros_ec_hwmon_info,
|
||||
};
|
||||
|
||||
static void cros_ec_hwmon_probe_temp_sensors(struct device *dev, struct cros_ec_hwmon_priv *priv,
|
||||
u8 thermal_version)
|
||||
{
|
||||
struct ec_params_temp_sensor_get_info req = {};
|
||||
struct ec_response_temp_sensor_get_info resp;
|
||||
size_t candidates, i, sensor_name_size;
|
||||
int ret;
|
||||
u8 temp;
|
||||
|
||||
if (thermal_version < 2)
|
||||
candidates = EC_TEMP_SENSOR_ENTRIES;
|
||||
else
|
||||
candidates = ARRAY_SIZE(priv->temp_sensor_names);
|
||||
|
||||
for (i = 0; i < candidates; i++) {
|
||||
if (cros_ec_hwmon_read_temp(priv->cros_ec, i, &temp) < 0)
|
||||
continue;
|
||||
|
||||
if (temp == EC_TEMP_SENSOR_NOT_PRESENT)
|
||||
continue;
|
||||
|
||||
req.id = i;
|
||||
ret = cros_ec_cmd(priv->cros_ec, 0, EC_CMD_TEMP_SENSOR_GET_INFO,
|
||||
&req, sizeof(req), &resp, sizeof(resp));
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
sensor_name_size = strnlen(resp.sensor_name, sizeof(resp.sensor_name));
|
||||
priv->temp_sensor_names[i] = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
|
||||
(int)sensor_name_size,
|
||||
resp.sensor_name);
|
||||
}
|
||||
}
|
||||
|
||||
static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
|
||||
{
|
||||
u16 speed;
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
|
||||
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, i, &speed);
|
||||
if (ret == 0 && speed != EC_FAN_SPEED_NOT_PRESENT)
|
||||
priv->usable_fans |= BIT(i);
|
||||
}
|
||||
}
|
||||
|
||||
static int cros_ec_hwmon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
|
||||
struct cros_ec_device *cros_ec = ec_dev->ec_dev;
|
||||
struct cros_ec_hwmon_priv *priv;
|
||||
struct device *hwmon_dev;
|
||||
u8 thermal_version;
|
||||
int ret;
|
||||
|
||||
ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_THERMAL_VERSION, 1, &thermal_version);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Covers both fan and temp sensors */
|
||||
if (thermal_version == 0)
|
||||
return -ENODEV;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->cros_ec = cros_ec;
|
||||
|
||||
cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version);
|
||||
cros_ec_hwmon_probe_fans(priv);
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
|
||||
&cros_ec_hwmon_chip_info, NULL);
|
||||
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct platform_device_id cros_ec_hwmon_id[] = {
|
||||
{ DRV_NAME, 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver cros_ec_hwmon_driver = {
|
||||
.driver.name = DRV_NAME,
|
||||
.probe = cros_ec_hwmon_probe,
|
||||
.id_table = cros_ec_hwmon_id,
|
||||
};
|
||||
module_platform_driver(cros_ec_hwmon_driver);
|
||||
|
||||
MODULE_DEVICE_TABLE(platform, cros_ec_hwmon_id);
|
||||
MODULE_DESCRIPTION("ChromeOS EC Hardware Monitoring Driver");
|
||||
MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -388,8 +388,8 @@ EXPORT_SYMBOL(cros_ec_suspend_late);
|
|||
*/
|
||||
int cros_ec_suspend(struct cros_ec_device *ec_dev)
|
||||
{
|
||||
cros_ec_send_suspend_event(ec_dev);
|
||||
cros_ec_disable_irq(ec_dev);
|
||||
cros_ec_suspend_prepare(ec_dev);
|
||||
cros_ec_suspend_late(ec_dev);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(cros_ec_suspend);
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
|
||||
#define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1))
|
||||
|
||||
static unsigned int log_poll_period_ms = LOG_POLL_SEC * MSEC_PER_SEC;
|
||||
module_param(log_poll_period_ms, uint, 0644);
|
||||
MODULE_PARM_DESC(log_poll_period_ms, "EC log polling period(ms)");
|
||||
|
||||
/* waitqueue for log readers */
|
||||
static DECLARE_WAIT_QUEUE_HEAD(cros_ec_debugfs_log_wq);
|
||||
|
||||
|
@ -57,7 +61,7 @@ struct cros_ec_debugfs {
|
|||
|
||||
/*
|
||||
* We need to make sure that the EC log buffer on the UART is large enough,
|
||||
* so that it is unlikely enough to overlow within LOG_POLL_SEC.
|
||||
* so that it is unlikely enough to overlow within log_poll_period_ms.
|
||||
*/
|
||||
static void cros_ec_console_log_work(struct work_struct *__work)
|
||||
{
|
||||
|
@ -119,7 +123,7 @@ static void cros_ec_console_log_work(struct work_struct *__work)
|
|||
|
||||
resched:
|
||||
schedule_delayed_work(&debug_info->log_poll_work,
|
||||
msecs_to_jiffies(LOG_POLL_SEC * 1000));
|
||||
msecs_to_jiffies(log_poll_period_ms));
|
||||
}
|
||||
|
||||
static int cros_ec_console_log_open(struct inode *inode, struct file *file)
|
||||
|
@ -330,6 +334,7 @@ static int ec_read_version_supported(struct cros_ec_dev *ec)
|
|||
if (!msg)
|
||||
return 0;
|
||||
|
||||
msg->version = 1;
|
||||
msg->command = EC_CMD_GET_CMD_VERSIONS + ec->cmd_offset;
|
||||
msg->outsize = sizeof(*params);
|
||||
msg->insize = sizeof(*response);
|
||||
|
|
|
@ -39,6 +39,16 @@ static bool cros_ec_lpc_acpi_device_found;
|
|||
* be used as the base port for EC mapped memory.
|
||||
*/
|
||||
#define CROS_EC_LPC_QUIRK_REMAP_MEMORY BIT(0)
|
||||
/*
|
||||
* Indicates that lpc_driver_data.quirk_acpi_id should be used to find
|
||||
* the ACPI device.
|
||||
*/
|
||||
#define CROS_EC_LPC_QUIRK_ACPI_ID BIT(1)
|
||||
/*
|
||||
* Indicates that lpc_driver_data.quirk_aml_mutex_name should be used
|
||||
* to find an AML mutex to protect access to Microchip EC.
|
||||
*/
|
||||
#define CROS_EC_LPC_QUIRK_AML_MUTEX BIT(2)
|
||||
|
||||
/**
|
||||
* struct lpc_driver_data - driver data attached to a DMI device ID to indicate
|
||||
|
@ -46,10 +56,15 @@ static bool cros_ec_lpc_acpi_device_found;
|
|||
* @quirks: a bitfield composed of quirks from CROS_EC_LPC_QUIRK_*
|
||||
* @quirk_mmio_memory_base: The first I/O port addressing EC mapped memory (used
|
||||
* when quirk ...REMAP_MEMORY is set.)
|
||||
* @quirk_acpi_id: An ACPI HID to be used to find the ACPI device.
|
||||
* @quirk_aml_mutex_name: The name of an AML mutex to be used to protect access
|
||||
* to Microchip EC.
|
||||
*/
|
||||
struct lpc_driver_data {
|
||||
u32 quirks;
|
||||
u16 quirk_mmio_memory_base;
|
||||
const char *quirk_acpi_id;
|
||||
const char *quirk_aml_mutex_name;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -62,14 +77,16 @@ struct cros_ec_lpc {
|
|||
|
||||
/**
|
||||
* struct lpc_driver_ops - LPC driver operations
|
||||
* @read: Copy length bytes from EC address offset into buffer dest. Returns
|
||||
* the 8-bit checksum of all bytes read.
|
||||
* @write: Copy length bytes from buffer msg into EC address offset. Returns
|
||||
* the 8-bit checksum of all bytes written.
|
||||
* @read: Copy length bytes from EC address offset into buffer dest.
|
||||
* Returns a negative error code on error, or the 8-bit checksum
|
||||
* of all bytes read.
|
||||
* @write: Copy length bytes from buffer msg into EC address offset.
|
||||
* Returns a negative error code on error, or the 8-bit checksum
|
||||
* of all bytes written.
|
||||
*/
|
||||
struct lpc_driver_ops {
|
||||
u8 (*read)(unsigned int offset, unsigned int length, u8 *dest);
|
||||
u8 (*write)(unsigned int offset, unsigned int length, const u8 *msg);
|
||||
int (*read)(unsigned int offset, unsigned int length, u8 *dest);
|
||||
int (*write)(unsigned int offset, unsigned int length, const u8 *msg);
|
||||
};
|
||||
|
||||
static struct lpc_driver_ops cros_ec_lpc_ops = { };
|
||||
|
@ -78,10 +95,10 @@ static struct lpc_driver_ops cros_ec_lpc_ops = { };
|
|||
* A generic instance of the read function of struct lpc_driver_ops, used for
|
||||
* the LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length,
|
||||
u8 *dest)
|
||||
static int cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length,
|
||||
u8 *dest)
|
||||
{
|
||||
int sum = 0;
|
||||
u8 sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
|
@ -97,10 +114,10 @@ static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length,
|
|||
* A generic instance of the write function of struct lpc_driver_ops, used for
|
||||
* the LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length,
|
||||
const u8 *msg)
|
||||
static int cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length,
|
||||
const u8 *msg)
|
||||
{
|
||||
int sum = 0;
|
||||
u8 sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
|
@ -116,13 +133,13 @@ static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length,
|
|||
* An instance of the read function of struct lpc_driver_ops, used for the
|
||||
* MEC variant of LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length,
|
||||
u8 *dest)
|
||||
static int cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length,
|
||||
u8 *dest)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
return in_range;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
|
||||
|
@ -135,13 +152,13 @@ static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length,
|
|||
* An instance of the write function of struct lpc_driver_ops, used for the
|
||||
* MEC variant of LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length,
|
||||
const u8 *msg)
|
||||
static int cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length,
|
||||
const u8 *msg)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
return in_range;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
|
||||
|
@ -154,11 +171,14 @@ static int ec_response_timed_out(void)
|
|||
{
|
||||
unsigned long one_second = jiffies + HZ;
|
||||
u8 data;
|
||||
int ret;
|
||||
|
||||
usleep_range(200, 300);
|
||||
do {
|
||||
if (!(cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data) &
|
||||
EC_LPC_STATUS_BUSY_MASK))
|
||||
ret = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!(data & EC_LPC_STATUS_BUSY_MASK))
|
||||
return 0;
|
||||
usleep_range(100, 200);
|
||||
} while (time_before(jiffies, one_second));
|
||||
|
@ -179,28 +199,41 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
|||
goto done;
|
||||
|
||||
/* Write buffer */
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
|
||||
ret = cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
/* Here we go */
|
||||
sum = EC_COMMAND_PROTOCOL_3;
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
ret = cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
if (ec_response_timed_out()) {
|
||||
ret = ec_response_timed_out();
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
if (ret) {
|
||||
dev_warn(ec->dev, "EC response timed out\n");
|
||||
ret = -EIO;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Check result */
|
||||
msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
ret = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
msg->result = ret;
|
||||
ret = cros_ec_check_result(ec, msg);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
/* Read back response */
|
||||
dout = (u8 *)&response;
|
||||
sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
||||
ret = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
||||
dout);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
sum = ret;
|
||||
|
||||
msg->result = response.result;
|
||||
|
||||
|
@ -213,9 +246,12 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
|||
}
|
||||
|
||||
/* Read response and process checksum */
|
||||
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET +
|
||||
sizeof(response), response.data_len,
|
||||
msg->data);
|
||||
ret = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET +
|
||||
sizeof(response), response.data_len,
|
||||
msg->data);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
sum += ret;
|
||||
|
||||
if (sum) {
|
||||
dev_err(ec->dev,
|
||||
|
@ -255,32 +291,47 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
|||
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||
|
||||
/* Copy data and update checksum */
|
||||
sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
||||
msg->data);
|
||||
ret = cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
||||
msg->data);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
sum += ret;
|
||||
|
||||
/* Finalize checksum and write args */
|
||||
args.checksum = sum;
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||
(u8 *)&args);
|
||||
ret = cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||
(u8 *)&args);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
/* Here we go */
|
||||
sum = msg->command;
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
ret = cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
if (ec_response_timed_out()) {
|
||||
ret = ec_response_timed_out();
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
if (ret) {
|
||||
dev_warn(ec->dev, "EC response timed out\n");
|
||||
ret = -EIO;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Check result */
|
||||
msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
ret = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
msg->result = ret;
|
||||
ret = cros_ec_check_result(ec, msg);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
/* Read back args */
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args);
|
||||
ret = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
if (args.data_size > msg->insize) {
|
||||
dev_err(ec->dev,
|
||||
|
@ -294,8 +345,11 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
|||
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||
|
||||
/* Read response and update checksum */
|
||||
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
||||
msg->data);
|
||||
ret = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
||||
msg->data);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
sum += ret;
|
||||
|
||||
/* Verify checksum */
|
||||
if (args.checksum != sum) {
|
||||
|
@ -320,19 +374,24 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
|
|||
int i = offset;
|
||||
char *s = dest;
|
||||
int cnt = 0;
|
||||
int ret;
|
||||
|
||||
if (offset >= EC_MEMMAP_SIZE - bytes)
|
||||
return -EINVAL;
|
||||
|
||||
/* fixed length */
|
||||
if (bytes) {
|
||||
cros_ec_lpc_ops.read(ec_lpc->mmio_memory_base + offset, bytes, s);
|
||||
ret = cros_ec_lpc_ops.read(ec_lpc->mmio_memory_base + offset, bytes, s);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* string */
|
||||
for (; i < EC_MEMMAP_SIZE; i++, s++) {
|
||||
cros_ec_lpc_ops.read(ec_lpc->mmio_memory_base + i, 1, s);
|
||||
ret = cros_ec_lpc_ops.read(ec_lpc->mmio_memory_base + i, 1, s);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
cnt++;
|
||||
if (!*s)
|
||||
break;
|
||||
|
@ -374,6 +433,26 @@ static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data)
|
|||
pm_system_wakeup();
|
||||
}
|
||||
|
||||
static acpi_status cros_ec_lpc_parse_device(acpi_handle handle, u32 level,
|
||||
void *context, void **retval)
|
||||
{
|
||||
*(struct acpi_device **)context = acpi_fetch_acpi_dev(handle);
|
||||
return AE_CTRL_TERMINATE;
|
||||
}
|
||||
|
||||
static struct acpi_device *cros_ec_lpc_get_device(const char *id)
|
||||
{
|
||||
struct acpi_device *adev = NULL;
|
||||
acpi_status status = acpi_get_devices(id, cros_ec_lpc_parse_device,
|
||||
&adev, NULL);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_warn(DRV_NAME ": Looking for %s failed\n", id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return adev;
|
||||
}
|
||||
|
||||
static int cros_ec_lpc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
@ -401,6 +480,27 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
|
|||
|
||||
if (quirks & CROS_EC_LPC_QUIRK_REMAP_MEMORY)
|
||||
ec_lpc->mmio_memory_base = driver_data->quirk_mmio_memory_base;
|
||||
|
||||
if (quirks & CROS_EC_LPC_QUIRK_ACPI_ID) {
|
||||
adev = cros_ec_lpc_get_device(driver_data->quirk_acpi_id);
|
||||
if (!adev) {
|
||||
dev_err(dev, "failed to get ACPI device '%s'",
|
||||
driver_data->quirk_acpi_id);
|
||||
return -ENODEV;
|
||||
}
|
||||
ACPI_COMPANION_SET(dev, adev);
|
||||
}
|
||||
|
||||
if (quirks & CROS_EC_LPC_QUIRK_AML_MUTEX) {
|
||||
const char *name
|
||||
= driver_data->quirk_aml_mutex_name;
|
||||
ret = cros_ec_lpc_mec_acpi_mutex(ACPI_COMPANION(dev), name);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to get AML mutex '%s'", name);
|
||||
return ret;
|
||||
}
|
||||
dev_info(dev, "got AML mutex '%s'", name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -425,7 +525,9 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
|
|||
*/
|
||||
cros_ec_lpc_ops.read = cros_ec_lpc_mec_read_bytes;
|
||||
cros_ec_lpc_ops.write = cros_ec_lpc_mec_write_bytes;
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
|
||||
ret = cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (buf[0] != 'E' || buf[1] != 'C') {
|
||||
if (!devm_request_region(dev, ec_lpc->mmio_memory_base, EC_MEMMAP_SIZE,
|
||||
dev_name(dev))) {
|
||||
|
@ -436,8 +538,10 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
|
|||
/* Re-assign read/write operations for the non MEC variant */
|
||||
cros_ec_lpc_ops.read = cros_ec_lpc_read_bytes;
|
||||
cros_ec_lpc_ops.write = cros_ec_lpc_write_bytes;
|
||||
cros_ec_lpc_ops.read(ec_lpc->mmio_memory_base + EC_MEMMAP_ID, 2,
|
||||
buf);
|
||||
ret = cros_ec_lpc_ops.read(ec_lpc->mmio_memory_base + EC_MEMMAP_ID, 2,
|
||||
buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (buf[0] != 'E' || buf[1] != 'C') {
|
||||
dev_err(dev, "EC ID not detected\n");
|
||||
return -ENODEV;
|
||||
|
@ -532,6 +636,12 @@ static const struct lpc_driver_data framework_laptop_amd_lpc_driver_data __initc
|
|||
.quirk_mmio_memory_base = 0xE00,
|
||||
};
|
||||
|
||||
static const struct lpc_driver_data framework_laptop_11_lpc_driver_data __initconst = {
|
||||
.quirks = CROS_EC_LPC_QUIRK_ACPI_ID|CROS_EC_LPC_QUIRK_AML_MUTEX,
|
||||
.quirk_acpi_id = "PNP0C09",
|
||||
.quirk_aml_mutex_name = "ECMT",
|
||||
};
|
||||
|
||||
static const struct dmi_system_id cros_ec_lpc_dmi_table[] __initconst = {
|
||||
{
|
||||
/*
|
||||
|
@ -600,6 +710,7 @@ static const struct dmi_system_id cros_ec_lpc_dmi_table[] __initconst = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "Framework"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Laptop"),
|
||||
},
|
||||
.driver_data = (void *)&framework_laptop_11_lpc_driver_data,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
@ -661,23 +772,12 @@ static struct platform_device cros_ec_lpc_device = {
|
|||
.name = DRV_NAME
|
||||
};
|
||||
|
||||
static acpi_status cros_ec_lpc_parse_device(acpi_handle handle, u32 level,
|
||||
void *context, void **retval)
|
||||
{
|
||||
*(bool *)context = true;
|
||||
return AE_CTRL_TERMINATE;
|
||||
}
|
||||
|
||||
static int __init cros_ec_lpc_init(void)
|
||||
{
|
||||
int ret;
|
||||
acpi_status status;
|
||||
const struct dmi_system_id *dmi_match;
|
||||
|
||||
status = acpi_get_devices(ACPI_DRV_NAME, cros_ec_lpc_parse_device,
|
||||
&cros_ec_lpc_acpi_device_found, NULL);
|
||||
if (ACPI_FAILURE(status))
|
||||
pr_warn(DRV_NAME ": Looking for %s failed\n", ACPI_DRV_NAME);
|
||||
cros_ec_lpc_acpi_device_found = !!cros_ec_lpc_get_device(ACPI_DRV_NAME);
|
||||
|
||||
dmi_match = dmi_first_match(cros_ec_lpc_dmi_table);
|
||||
|
||||
|
|
|
@ -10,13 +10,65 @@
|
|||
|
||||
#include "cros_ec_lpc_mec.h"
|
||||
|
||||
#define ACPI_LOCK_DELAY_MS 500
|
||||
|
||||
/*
|
||||
* This mutex must be held while accessing the EMI unit. We can't rely on the
|
||||
* EC mutex because memmap data may be accessed without it being held.
|
||||
*/
|
||||
static DEFINE_MUTEX(io_mutex);
|
||||
/*
|
||||
* An alternative mutex to be used when the ACPI AML code may also
|
||||
* access memmap data. When set, this mutex is used in preference to
|
||||
* io_mutex.
|
||||
*/
|
||||
static acpi_handle aml_mutex;
|
||||
|
||||
static u16 mec_emi_base, mec_emi_end;
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_mec_lock() - Acquire mutex for EMI
|
||||
*
|
||||
* @return: Negative error code, or zero for success
|
||||
*/
|
||||
static int cros_ec_lpc_mec_lock(void)
|
||||
{
|
||||
bool success;
|
||||
|
||||
if (!aml_mutex) {
|
||||
mutex_lock(&io_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
success = ACPI_SUCCESS(acpi_acquire_mutex(aml_mutex,
|
||||
NULL, ACPI_LOCK_DELAY_MS));
|
||||
if (!success)
|
||||
return -EBUSY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_mec_unlock() - Release mutex for EMI
|
||||
*
|
||||
* @return: Negative error code, or zero for success
|
||||
*/
|
||||
static int cros_ec_lpc_mec_unlock(void)
|
||||
{
|
||||
bool success;
|
||||
|
||||
if (!aml_mutex) {
|
||||
mutex_unlock(&io_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
success = ACPI_SUCCESS(acpi_release_mutex(aml_mutex, NULL));
|
||||
if (!success)
|
||||
return -EBUSY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_mec_emi_write_address() - Initialize EMI at a given address.
|
||||
*
|
||||
|
@ -41,9 +93,6 @@ static void cros_ec_lpc_mec_emi_write_address(u16 addr,
|
|||
*/
|
||||
int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length)
|
||||
{
|
||||
if (length == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (WARN_ON(mec_emi_base == 0 || mec_emi_end == 0))
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -67,16 +116,21 @@ int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length)
|
|||
* @length: Number of bytes to read / write
|
||||
* @buf: Destination / source buffer
|
||||
*
|
||||
* Return: 8-bit checksum of all bytes read / written
|
||||
* @return: A negative error code on error, or 8-bit checksum of all
|
||||
* bytes read / written
|
||||
*/
|
||||
u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
||||
unsigned int offset, unsigned int length,
|
||||
u8 *buf)
|
||||
int cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
||||
unsigned int offset, unsigned int length,
|
||||
u8 *buf)
|
||||
{
|
||||
int i = 0;
|
||||
int io_addr;
|
||||
u8 sum = 0;
|
||||
enum cros_ec_lpc_mec_emi_access_mode access, new_access;
|
||||
int ret;
|
||||
|
||||
if (length == 0)
|
||||
return 0;
|
||||
|
||||
/* Return checksum of 0 if window is not initialized */
|
||||
WARN_ON(mec_emi_base == 0 || mec_emi_end == 0);
|
||||
|
@ -92,7 +146,9 @@ u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
|||
else
|
||||
access = ACCESS_TYPE_LONG_AUTO_INCREMENT;
|
||||
|
||||
mutex_lock(&io_mutex);
|
||||
ret = cros_ec_lpc_mec_lock();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Initialize I/O at desired address */
|
||||
cros_ec_lpc_mec_emi_write_address(offset, access);
|
||||
|
@ -134,7 +190,9 @@ u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
|||
}
|
||||
|
||||
done:
|
||||
mutex_unlock(&io_mutex);
|
||||
ret = cros_ec_lpc_mec_unlock();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
@ -146,3 +204,18 @@ void cros_ec_lpc_mec_init(unsigned int base, unsigned int end)
|
|||
mec_emi_end = end;
|
||||
}
|
||||
EXPORT_SYMBOL(cros_ec_lpc_mec_init);
|
||||
|
||||
int cros_ec_lpc_mec_acpi_mutex(struct acpi_device *adev, const char *pathname)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (!adev)
|
||||
return -ENOENT;
|
||||
|
||||
status = acpi_get_handle(adev->handle, pathname, &aml_mutex);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(cros_ec_lpc_mec_acpi_mutex);
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#ifndef __CROS_EC_LPC_MEC_H
|
||||
#define __CROS_EC_LPC_MEC_H
|
||||
|
||||
#include <linux/acpi.h>
|
||||
|
||||
enum cros_ec_lpc_mec_emi_access_mode {
|
||||
/* 8-bit access */
|
||||
ACCESS_TYPE_BYTE = 0x0,
|
||||
|
@ -45,6 +47,15 @@ enum cros_ec_lpc_mec_io_type {
|
|||
*/
|
||||
void cros_ec_lpc_mec_init(unsigned int base, unsigned int end);
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_mec_acpi_mutex() - Find and set ACPI mutex for MEC
|
||||
*
|
||||
* @adev: Parent ACPI device
|
||||
* @pathname: Name of AML mutex
|
||||
* @return: Negative error code, or zero for success
|
||||
*/
|
||||
int cros_ec_lpc_mec_acpi_mutex(struct acpi_device *adev, const char *pathname);
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_mec_in_range() - Determine if addresses are in MEC EMI range.
|
||||
*
|
||||
|
@ -64,9 +75,10 @@ int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length);
|
|||
* @length: Number of bytes to read / write
|
||||
* @buf: Destination / source buffer
|
||||
*
|
||||
* @return 8-bit checksum of all bytes read / written
|
||||
* @return: A negative error code on error, or 8-bit checksum of all
|
||||
* bytes read / written
|
||||
*/
|
||||
u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
||||
unsigned int offset, unsigned int length, u8 *buf);
|
||||
int cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
||||
unsigned int offset, unsigned int length, u8 *buf);
|
||||
|
||||
#endif /* __CROS_EC_LPC_MEC_H */
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/cros_ec_commands.h>
|
||||
#include <linux/platform_data/cros_ec_proto.h>
|
||||
|
@ -239,13 +240,12 @@ int cros_ec_check_result(struct cros_ec_device *ec_dev,
|
|||
}
|
||||
EXPORT_SYMBOL(cros_ec_check_result);
|
||||
|
||||
/*
|
||||
/**
|
||||
* cros_ec_get_host_event_wake_mask
|
||||
*
|
||||
* Get the mask of host events that cause wake from suspend.
|
||||
*
|
||||
* @ec_dev: EC device to call
|
||||
* @msg: message structure to use
|
||||
* @mask: result when function returns 0.
|
||||
*
|
||||
* LOCKING:
|
||||
|
@ -427,13 +427,12 @@ static int cros_ec_get_proto_info_legacy(struct cros_ec_device *ec_dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* cros_ec_get_host_command_version_mask
|
||||
*
|
||||
* Get the version mask of a given command.
|
||||
*
|
||||
* @ec_dev: EC device to call
|
||||
* @msg: message structure to use
|
||||
* @cmd: command to get the version of.
|
||||
* @mask: result when function returns 0.
|
||||
*
|
||||
|
@ -686,7 +685,7 @@ EXPORT_SYMBOL(cros_ec_cmd_xfer_status);
|
|||
|
||||
static int get_next_event_xfer(struct cros_ec_device *ec_dev,
|
||||
struct cros_ec_command *msg,
|
||||
struct ec_response_get_next_event_v1 *event,
|
||||
struct ec_response_get_next_event_v3 *event,
|
||||
int version, uint32_t size)
|
||||
{
|
||||
int ret;
|
||||
|
@ -709,11 +708,12 @@ static int get_next_event(struct cros_ec_device *ec_dev)
|
|||
{
|
||||
struct {
|
||||
struct cros_ec_command msg;
|
||||
struct ec_response_get_next_event_v1 event;
|
||||
struct ec_response_get_next_event_v3 event;
|
||||
} __packed buf;
|
||||
struct cros_ec_command *msg = &buf.msg;
|
||||
struct ec_response_get_next_event_v1 *event = &buf.event;
|
||||
const int cmd_version = ec_dev->mkbp_event_supported - 1;
|
||||
struct ec_response_get_next_event_v3 *event = &buf.event;
|
||||
int cmd_version = ec_dev->mkbp_event_supported - 1;
|
||||
u32 size;
|
||||
|
||||
memset(msg, 0, sizeof(*msg));
|
||||
if (ec_dev->suspended) {
|
||||
|
@ -721,12 +721,20 @@ static int get_next_event(struct cros_ec_device *ec_dev)
|
|||
return -EHOSTDOWN;
|
||||
}
|
||||
|
||||
if (cmd_version == 0)
|
||||
return get_next_event_xfer(ec_dev, msg, event, 0,
|
||||
sizeof(struct ec_response_get_next_event));
|
||||
if (cmd_version == 0) {
|
||||
size = sizeof(struct ec_response_get_next_event);
|
||||
} else if (cmd_version < 3) {
|
||||
size = sizeof(struct ec_response_get_next_event_v1);
|
||||
} else {
|
||||
/*
|
||||
* The max version we support is v3. So, we speak v3 even if the
|
||||
* EC says it supports v4+.
|
||||
*/
|
||||
cmd_version = 3;
|
||||
size = sizeof(struct ec_response_get_next_event_v3);
|
||||
}
|
||||
|
||||
return get_next_event_xfer(ec_dev, msg, event, cmd_version,
|
||||
sizeof(struct ec_response_get_next_event_v1));
|
||||
return get_next_event_xfer(ec_dev, msg, event, cmd_version, size);
|
||||
}
|
||||
|
||||
static int get_keyboard_state_event(struct cros_ec_device *ec_dev)
|
||||
|
@ -1035,3 +1043,64 @@ int cros_ec_cmd(struct cros_ec_device *ec_dev,
|
|||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_cmd);
|
||||
|
||||
/**
|
||||
* cros_ec_cmd_readmem - Read from EC memory.
|
||||
*
|
||||
* @ec_dev: EC device
|
||||
* @offset: Is within EC_LPC_ADDR_MEMMAP region.
|
||||
* @size: Number of bytes to read.
|
||||
* @dest: EC command output data
|
||||
*
|
||||
* Return: >= 0 on success, negative error number on failure.
|
||||
*/
|
||||
int cros_ec_cmd_readmem(struct cros_ec_device *ec_dev, u8 offset, u8 size, void *dest)
|
||||
{
|
||||
struct ec_params_read_memmap params = {};
|
||||
|
||||
if (!size)
|
||||
return -EINVAL;
|
||||
|
||||
if (ec_dev->cmd_readmem)
|
||||
return ec_dev->cmd_readmem(ec_dev, offset, size, dest);
|
||||
|
||||
params.offset = offset;
|
||||
params.size = size;
|
||||
return cros_ec_cmd(ec_dev, 0, EC_CMD_READ_MEMMAP,
|
||||
¶ms, sizeof(params), dest, size);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_cmd_readmem);
|
||||
|
||||
/**
|
||||
* cros_ec_get_cmd_versions - Get supported version mask.
|
||||
*
|
||||
* @ec_dev: EC device
|
||||
* @cmd: Command to test
|
||||
*
|
||||
* Return: version mask on success, negative error number on failure.
|
||||
*/
|
||||
int cros_ec_get_cmd_versions(struct cros_ec_device *ec_dev, u16 cmd)
|
||||
{
|
||||
struct ec_params_get_cmd_versions req_v0;
|
||||
struct ec_params_get_cmd_versions_v1 req_v1;
|
||||
struct ec_response_get_cmd_versions resp;
|
||||
int ret;
|
||||
|
||||
if (cmd <= U8_MAX) {
|
||||
req_v0.cmd = cmd;
|
||||
ret = cros_ec_cmd(ec_dev, 0, EC_CMD_GET_CMD_VERSIONS,
|
||||
&req_v0, sizeof(req_v0), &resp, sizeof(resp));
|
||||
} else {
|
||||
req_v1.cmd = cmd;
|
||||
ret = cros_ec_cmd(ec_dev, 1, EC_CMD_GET_CMD_VERSIONS,
|
||||
&req_v1, sizeof(req_v1), &resp, sizeof(resp));
|
||||
}
|
||||
|
||||
if (ret == -EINVAL)
|
||||
return 0; /* Command not implemented */
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
else
|
||||
return resp.version_mask;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_get_cmd_versions);
|
||||
|
|
|
@ -2060,17 +2060,17 @@ static void cros_ec_proto_test_get_next_event_no_mkbp_event(struct kunit *test)
|
|||
|
||||
/* For get_keyboard_state_event(). */
|
||||
{
|
||||
union ec_response_get_next_data_v1 *data;
|
||||
union ec_response_get_next_data_v3 *data;
|
||||
|
||||
mock = cros_kunit_ec_xfer_mock_add(test, sizeof(*data));
|
||||
KUNIT_ASSERT_PTR_NE(test, mock, NULL);
|
||||
|
||||
data = (union ec_response_get_next_data_v1 *)mock->o_data;
|
||||
data = (union ec_response_get_next_data_v3 *)mock->o_data;
|
||||
data->host_event = 0xbeef;
|
||||
}
|
||||
|
||||
ret = cros_ec_get_next_event(ec_dev, &wake_event, &more_events);
|
||||
KUNIT_EXPECT_EQ(test, ret, sizeof(union ec_response_get_next_data_v1));
|
||||
KUNIT_EXPECT_EQ(test, ret, sizeof(union ec_response_get_next_data_v3));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, ec_dev->event_data.event_type, EC_MKBP_EVENT_KEY_MATRIX);
|
||||
KUNIT_EXPECT_EQ(test, ec_dev->event_data.data.host_event, 0xbeef);
|
||||
|
@ -2085,7 +2085,7 @@ static void cros_ec_proto_test_get_next_event_no_mkbp_event(struct kunit *test)
|
|||
|
||||
KUNIT_EXPECT_EQ(test, mock->msg.version, 0);
|
||||
KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_MKBP_STATE);
|
||||
KUNIT_EXPECT_EQ(test, mock->msg.insize, sizeof(union ec_response_get_next_data_v1));
|
||||
KUNIT_EXPECT_EQ(test, mock->msg.insize, sizeof(union ec_response_get_next_data_v3));
|
||||
KUNIT_EXPECT_EQ(test, mock->msg.outsize, 0);
|
||||
}
|
||||
}
|
||||
|
@ -2740,4 +2740,5 @@ static struct kunit_suite cros_ec_proto_test_suite = {
|
|||
|
||||
kunit_test_suite(cros_ec_proto_test_suite);
|
||||
|
||||
MODULE_DESCRIPTION("Kunit tests for ChromeOS Embedded Controller protocol");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -117,13 +117,17 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
|||
struct wilco_ec_request *rq)
|
||||
{
|
||||
struct wilco_ec_response *rs;
|
||||
u8 checksum;
|
||||
int ret;
|
||||
u8 flag;
|
||||
|
||||
/* Write request header, then data */
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq);
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, sizeof(*rq), msg->request_size,
|
||||
msg->request_data);
|
||||
ret = cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, sizeof(*rq), msg->request_size,
|
||||
msg->request_data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Start the command */
|
||||
outb(EC_MAILBOX_START_COMMAND, ec->io_command->start);
|
||||
|
@ -149,10 +153,12 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
|||
|
||||
/* Read back response */
|
||||
rs = ec->data_buffer;
|
||||
checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0,
|
||||
sizeof(*rs) + EC_MAILBOX_DATA_SIZE,
|
||||
(u8 *)rs);
|
||||
if (checksum) {
|
||||
ret = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0,
|
||||
sizeof(*rs) + EC_MAILBOX_DATA_SIZE,
|
||||
(u8 *)rs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret) {
|
||||
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
|
|
@ -860,6 +860,18 @@ config CHARGER_CROS_PCHG
|
|||
the peripheral charge ports from the EC and converts that into
|
||||
power_supply properties.
|
||||
|
||||
config CHARGER_CROS_CONTROL
|
||||
tristate "ChromeOS EC based charge control"
|
||||
depends on MFD_CROS_EC_DEV
|
||||
depends on ACPI_BATTERY
|
||||
default MFD_CROS_EC_DEV
|
||||
help
|
||||
Say Y here to enable ChromeOS EC based battery charge control.
|
||||
This driver can manage charge thresholds and behaviour.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called cros_charge-control.
|
||||
|
||||
config CHARGER_SC2731
|
||||
tristate "Spreadtrum SC2731 charger driver"
|
||||
depends on MFD_SC27XX_PMIC || COMPILE_TEST
|
||||
|
|
|
@ -100,6 +100,7 @@ obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
|
|||
obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
|
||||
obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
|
||||
obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
|
||||
obj-$(CONFIG_CHARGER_CROS_CONTROL) += cros_charge-control.o
|
||||
obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o
|
||||
obj-$(CONFIG_CHARGER_CROS_PCHG) += cros_peripheral_charger.o
|
||||
obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
|
||||
|
|
352
drivers/power/supply/cros_charge-control.c
Normal file
352
drivers/power/supply/cros_charge-control.c
Normal file
|
@ -0,0 +1,352 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* ChromeOS EC driver for charge control
|
||||
*
|
||||
* Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
|
||||
*/
|
||||
#include <acpi/battery.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/cros_ec_commands.h>
|
||||
#include <linux/platform_data/cros_ec_proto.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define EC_CHARGE_CONTROL_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
|
||||
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \
|
||||
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
|
||||
|
||||
enum CROS_CHCTL_ATTR {
|
||||
CROS_CHCTL_ATTR_START_THRESHOLD,
|
||||
CROS_CHCTL_ATTR_END_THRESHOLD,
|
||||
CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR,
|
||||
_CROS_CHCTL_ATTR_COUNT
|
||||
};
|
||||
|
||||
/*
|
||||
* Semantics of data *returned* from the EC API and Linux sysfs differ
|
||||
* slightly, also the v1 API can not return any data.
|
||||
* To match the expected sysfs API, data is never read back from the EC but
|
||||
* cached in the driver.
|
||||
*
|
||||
* Changes to the EC bypassing the driver will not be reflected in sysfs.
|
||||
* Any change to "charge_behaviour" will synchronize the EC with the driver state.
|
||||
*/
|
||||
|
||||
struct cros_chctl_priv {
|
||||
struct cros_ec_device *cros_ec;
|
||||
struct acpi_battery_hook battery_hook;
|
||||
struct power_supply *hooked_battery;
|
||||
u8 cmd_version;
|
||||
|
||||
/* The callbacks need to access this priv structure.
|
||||
* As neither the struct device nor power_supply are under the drivers
|
||||
* control, embed the attributes within priv to use with container_of().
|
||||
*/
|
||||
struct device_attribute device_attrs[_CROS_CHCTL_ATTR_COUNT];
|
||||
struct attribute *attributes[_CROS_CHCTL_ATTR_COUNT];
|
||||
struct attribute_group group;
|
||||
|
||||
enum power_supply_charge_behaviour current_behaviour;
|
||||
u8 current_start_threshold, current_end_threshold;
|
||||
};
|
||||
|
||||
static int cros_chctl_send_charge_control_cmd(struct cros_ec_device *cros_ec,
|
||||
u8 cmd_version, struct ec_params_charge_control *req)
|
||||
{
|
||||
static const u8 outsizes[] = {
|
||||
[1] = offsetof(struct ec_params_charge_control, cmd),
|
||||
[2] = sizeof(struct ec_params_charge_control),
|
||||
[3] = sizeof(struct ec_params_charge_control),
|
||||
};
|
||||
|
||||
struct {
|
||||
struct cros_ec_command msg;
|
||||
union {
|
||||
struct ec_params_charge_control req;
|
||||
struct ec_response_charge_control resp;
|
||||
} __packed data;
|
||||
} __packed buf = {
|
||||
.msg = {
|
||||
.command = EC_CMD_CHARGE_CONTROL,
|
||||
.version = cmd_version,
|
||||
.insize = 0,
|
||||
.outsize = outsizes[cmd_version],
|
||||
},
|
||||
.data.req = *req,
|
||||
};
|
||||
|
||||
return cros_ec_cmd_xfer_status(cros_ec, &buf.msg);
|
||||
}
|
||||
|
||||
static int cros_chctl_configure_ec(struct cros_chctl_priv *priv)
|
||||
{
|
||||
struct ec_params_charge_control req = {};
|
||||
|
||||
req.cmd = EC_CHARGE_CONTROL_CMD_SET;
|
||||
|
||||
switch (priv->current_behaviour) {
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
|
||||
req.mode = CHARGE_CONTROL_NORMAL;
|
||||
break;
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
|
||||
req.mode = CHARGE_CONTROL_IDLE;
|
||||
break;
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
|
||||
req.mode = CHARGE_CONTROL_DISCHARGE;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (priv->current_behaviour == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO &&
|
||||
!(priv->current_start_threshold == 0 && priv->current_end_threshold == 100)) {
|
||||
req.sustain_soc.lower = priv->current_start_threshold;
|
||||
req.sustain_soc.upper = priv->current_end_threshold;
|
||||
} else {
|
||||
/* Disable charging limits */
|
||||
req.sustain_soc.lower = -1;
|
||||
req.sustain_soc.upper = -1;
|
||||
}
|
||||
|
||||
return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version, &req);
|
||||
}
|
||||
|
||||
static struct cros_chctl_priv *cros_chctl_attr_to_priv(struct attribute *attr,
|
||||
enum CROS_CHCTL_ATTR idx)
|
||||
{
|
||||
struct device_attribute *dev_attr = container_of(attr, struct device_attribute, attr);
|
||||
|
||||
return container_of(dev_attr, struct cros_chctl_priv, device_attrs[idx]);
|
||||
}
|
||||
|
||||
static ssize_t cros_chctl_store_threshold(struct device *dev, struct cros_chctl_priv *priv,
|
||||
int is_end_threshold, const char *buf, size_t count)
|
||||
{
|
||||
int ret, val;
|
||||
|
||||
ret = kstrtoint(buf, 10, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (val < 0 || val > 100)
|
||||
return -EINVAL;
|
||||
|
||||
if (is_end_threshold) {
|
||||
if (val <= priv->current_start_threshold)
|
||||
return -EINVAL;
|
||||
priv->current_end_threshold = val;
|
||||
} else {
|
||||
if (val >= priv->current_end_threshold)
|
||||
return -EINVAL;
|
||||
priv->current_start_threshold = val;
|
||||
}
|
||||
|
||||
if (priv->current_behaviour == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) {
|
||||
ret = cros_chctl_configure_ec(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t charge_control_start_threshold_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr,
|
||||
CROS_CHCTL_ATTR_START_THRESHOLD);
|
||||
|
||||
return sysfs_emit(buf, "%u\n", (unsigned int)priv->current_start_threshold);
|
||||
}
|
||||
|
||||
static ssize_t charge_control_start_threshold_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr,
|
||||
CROS_CHCTL_ATTR_START_THRESHOLD);
|
||||
|
||||
return cros_chctl_store_threshold(dev, priv, 0, buf, count);
|
||||
}
|
||||
|
||||
static ssize_t charge_control_end_threshold_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr,
|
||||
CROS_CHCTL_ATTR_END_THRESHOLD);
|
||||
|
||||
return sysfs_emit(buf, "%u\n", (unsigned int)priv->current_end_threshold);
|
||||
}
|
||||
|
||||
static ssize_t charge_control_end_threshold_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr,
|
||||
CROS_CHCTL_ATTR_END_THRESHOLD);
|
||||
|
||||
return cros_chctl_store_threshold(dev, priv, 1, buf, count);
|
||||
}
|
||||
|
||||
static ssize_t charge_behaviour_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr,
|
||||
CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR);
|
||||
|
||||
return power_supply_charge_behaviour_show(dev, EC_CHARGE_CONTROL_BEHAVIOURS,
|
||||
priv->current_behaviour, buf);
|
||||
}
|
||||
|
||||
static ssize_t charge_behaviour_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(&attr->attr,
|
||||
CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR);
|
||||
int ret;
|
||||
|
||||
ret = power_supply_charge_behaviour_parse(EC_CHARGE_CONTROL_BEHAVIOURS, buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->current_behaviour = ret;
|
||||
|
||||
ret = cros_chctl_configure_ec(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static umode_t cros_chtl_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
|
||||
{
|
||||
struct cros_chctl_priv *priv = cros_chctl_attr_to_priv(attr, n);
|
||||
|
||||
if (priv->cmd_version < 2) {
|
||||
if (n == CROS_CHCTL_ATTR_START_THRESHOLD)
|
||||
return 0;
|
||||
if (n == CROS_CHCTL_ATTR_END_THRESHOLD)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
static int cros_chctl_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
|
||||
{
|
||||
struct cros_chctl_priv *priv = container_of(hook, struct cros_chctl_priv, battery_hook);
|
||||
|
||||
if (priv->hooked_battery)
|
||||
return 0;
|
||||
|
||||
priv->hooked_battery = battery;
|
||||
return device_add_group(&battery->dev, &priv->group);
|
||||
}
|
||||
|
||||
static int cros_chctl_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
|
||||
{
|
||||
struct cros_chctl_priv *priv = container_of(hook, struct cros_chctl_priv, battery_hook);
|
||||
|
||||
if (priv->hooked_battery == battery) {
|
||||
device_remove_group(&battery->dev, &priv->group);
|
||||
priv->hooked_battery = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool probe_with_fwk_charge_control;
|
||||
module_param(probe_with_fwk_charge_control, bool, 0644);
|
||||
MODULE_PARM_DESC(probe_with_fwk_charge_control,
|
||||
"Probe the driver in the presence of the custom Framework EC charge control");
|
||||
|
||||
static int cros_chctl_fwk_charge_control_versions(struct cros_ec_device *cros_ec)
|
||||
{
|
||||
if (!dmi_match(DMI_SYS_VENDOR, "Framework"))
|
||||
return 0;
|
||||
|
||||
return cros_ec_get_cmd_versions(cros_ec, 0x3E03 /* FW_EC_CMD_CHARGE_LIMIT */);
|
||||
}
|
||||
|
||||
static int cros_chctl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
|
||||
struct cros_ec_device *cros_ec = ec_dev->ec_dev;
|
||||
struct cros_chctl_priv *priv;
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
ret = cros_chctl_fwk_charge_control_versions(cros_ec);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret > 0 && !probe_with_fwk_charge_control) {
|
||||
dev_info(dev, "Framework charge control detected, preventing load\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = cros_ec_get_cmd_versions(cros_ec, EC_CMD_CHARGE_CONTROL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret & EC_VER_MASK(3))
|
||||
priv->cmd_version = 3;
|
||||
else if (ret & EC_VER_MASK(2))
|
||||
priv->cmd_version = 2;
|
||||
else if (ret & EC_VER_MASK(1))
|
||||
priv->cmd_version = 1;
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
dev_dbg(dev, "Command version: %u\n", (unsigned int)priv->cmd_version);
|
||||
|
||||
priv->cros_ec = cros_ec;
|
||||
priv->device_attrs[CROS_CHCTL_ATTR_START_THRESHOLD] =
|
||||
(struct device_attribute)__ATTR_RW(charge_control_start_threshold);
|
||||
priv->device_attrs[CROS_CHCTL_ATTR_END_THRESHOLD] =
|
||||
(struct device_attribute)__ATTR_RW(charge_control_end_threshold);
|
||||
priv->device_attrs[CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR] =
|
||||
(struct device_attribute)__ATTR_RW(charge_behaviour);
|
||||
for (i = 0; i < _CROS_CHCTL_ATTR_COUNT; i++) {
|
||||
sysfs_attr_init(&priv->device_attrs[i].attr);
|
||||
priv->attributes[i] = &priv->device_attrs[i].attr;
|
||||
}
|
||||
priv->group.is_visible = cros_chtl_attr_is_visible;
|
||||
priv->group.attrs = priv->attributes;
|
||||
|
||||
priv->battery_hook.name = dev_name(dev);
|
||||
priv->battery_hook.add_battery = cros_chctl_add_battery;
|
||||
priv->battery_hook.remove_battery = cros_chctl_remove_battery;
|
||||
|
||||
priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
|
||||
priv->current_start_threshold = 0;
|
||||
priv->current_end_threshold = 100;
|
||||
|
||||
/* Bring EC into well-known state */
|
||||
ret = cros_chctl_configure_ec(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return devm_battery_hook_register(dev, &priv->battery_hook);
|
||||
}
|
||||
|
||||
static const struct platform_device_id cros_chctl_id[] = {
|
||||
{ "cros-charge-control", 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver cros_chctl_driver = {
|
||||
.driver.name = "cros-charge-control",
|
||||
.probe = cros_chctl_probe,
|
||||
.id_table = cros_chctl_id,
|
||||
};
|
||||
module_platform_driver(cros_chctl_driver);
|
||||
|
||||
MODULE_DEVICE_TABLE(platform, cros_chctl_id);
|
||||
MODULE_DESCRIPTION("ChromeOS EC charge control");
|
||||
MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -2,6 +2,7 @@
|
|||
#ifndef __ACPI_BATTERY_H
|
||||
#define __ACPI_BATTERY_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/power_supply.h>
|
||||
|
||||
#define ACPI_BATTERY_CLASS "battery"
|
||||
|
@ -19,5 +20,6 @@ struct acpi_battery_hook {
|
|||
|
||||
void battery_hook_register(struct acpi_battery_hook *hook);
|
||||
void battery_hook_unregister(struct acpi_battery_hook *hook);
|
||||
int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -100,4 +100,108 @@
|
|||
MATRIX_KEY(0x07, 0x0b, KEY_UP) \
|
||||
MATRIX_KEY(0x07, 0x0c, KEY_LEFT)
|
||||
|
||||
/* No numpad */
|
||||
#define CROS_TOP_ROW_KEYMAP_V30 \
|
||||
MATRIX_KEY(0x00, 0x01, KEY_F11) /* T11 */ \
|
||||
MATRIX_KEY(0x00, 0x02, KEY_F1) /* T1 */ \
|
||||
MATRIX_KEY(0x00, 0x04, KEY_F10) /* T10 */ \
|
||||
MATRIX_KEY(0x00, 0x0b, KEY_F14) /* T14 */ \
|
||||
MATRIX_KEY(0x00, 0x0c, KEY_F15) /* T15 */ \
|
||||
MATRIX_KEY(0x01, 0x02, KEY_F4) /* T4 */ \
|
||||
MATRIX_KEY(0x01, 0x04, KEY_F7) /* T7 */ \
|
||||
MATRIX_KEY(0x01, 0x05, KEY_F12) /* T12 */ \
|
||||
MATRIX_KEY(0x01, 0x09, KEY_F9) /* T9 */ \
|
||||
MATRIX_KEY(0x02, 0x02, KEY_F3) /* T3 */ \
|
||||
MATRIX_KEY(0x02, 0x04, KEY_F6) /* T6 */ \
|
||||
MATRIX_KEY(0x02, 0x0b, KEY_F8) /* T8 */ \
|
||||
MATRIX_KEY(0x03, 0x02, KEY_F2) /* T2 */ \
|
||||
MATRIX_KEY(0x03, 0x05, KEY_F13) /* T13 */ \
|
||||
MATRIX_KEY(0x04, 0x04, KEY_F5) /* T5 */
|
||||
|
||||
#define CROS_MAIN_KEYMAP_V30 /* Keycode */ \
|
||||
MATRIX_KEY(0x00, 0x03, KEY_B) /* 50 */ \
|
||||
MATRIX_KEY(0x00, 0x05, KEY_N) /* 51 */ \
|
||||
MATRIX_KEY(0x00, 0x06, KEY_RO) /* 56 (JIS) */ \
|
||||
MATRIX_KEY(0x00, 0x08, KEY_EQUAL) /* 13 */ \
|
||||
MATRIX_KEY(0x00, 0x09, KEY_HOME) /* 80 (Numpad) */ \
|
||||
MATRIX_KEY(0x00, 0x0a, KEY_RIGHTALT) /* 62 */ \
|
||||
MATRIX_KEY(0x00, 0x10, KEY_FN) /* 127 */ \
|
||||
\
|
||||
MATRIX_KEY(0x01, 0x01, KEY_ESC) /* 110 */ \
|
||||
MATRIX_KEY(0x01, 0x03, KEY_G) /* 35 */ \
|
||||
MATRIX_KEY(0x01, 0x06, KEY_H) /* 36 */ \
|
||||
MATRIX_KEY(0x01, 0x08, KEY_APOSTROPHE) /* 41 */ \
|
||||
MATRIX_KEY(0x01, 0x0b, KEY_BACKSPACE) /* 15 */ \
|
||||
MATRIX_KEY(0x01, 0x0c, KEY_HENKAN) /* 65 (JIS) */ \
|
||||
MATRIX_KEY(0x01, 0x0e, KEY_LEFTCTRL) /* 58 */ \
|
||||
\
|
||||
MATRIX_KEY(0x02, 0x01, KEY_TAB) /* 16 */ \
|
||||
MATRIX_KEY(0x02, 0x03, KEY_T) /* 21 */ \
|
||||
MATRIX_KEY(0x02, 0x05, KEY_RIGHTBRACE) /* 28 */ \
|
||||
MATRIX_KEY(0x02, 0x06, KEY_Y) /* 22 */ \
|
||||
MATRIX_KEY(0x02, 0x08, KEY_LEFTBRACE) /* 27 */ \
|
||||
MATRIX_KEY(0x02, 0x09, KEY_DELETE) /* 76 (Numpad) */ \
|
||||
MATRIX_KEY(0x02, 0x0c, KEY_PAGEUP) /* 85 (Numpad) */ \
|
||||
MATRIX_KEY(0x02, 0x011, KEY_YEN) /* 14 (JIS) */ \
|
||||
\
|
||||
MATRIX_KEY(0x03, 0x00, KEY_LEFTMETA) /* Launcher */ \
|
||||
MATRIX_KEY(0x03, 0x01, KEY_GRAVE) /* 1 */ \
|
||||
MATRIX_KEY(0x03, 0x03, KEY_5) /* 6 */ \
|
||||
MATRIX_KEY(0x03, 0x04, KEY_S) /* 32 */ \
|
||||
MATRIX_KEY(0x03, 0x06, KEY_MINUS) /* 12 */ \
|
||||
MATRIX_KEY(0x03, 0x08, KEY_6) /* 7 */ \
|
||||
MATRIX_KEY(0x03, 0x09, KEY_SLEEP) /* Lock */ \
|
||||
MATRIX_KEY(0x03, 0x0b, KEY_BACKSLASH) /* 29 */ \
|
||||
MATRIX_KEY(0x03, 0x0c, KEY_MUHENKAN) /* 63 (JIS) */ \
|
||||
MATRIX_KEY(0x03, 0x0e, KEY_RIGHTCTRL) /* 64 */ \
|
||||
\
|
||||
MATRIX_KEY(0x04, 0x01, KEY_A) /* 31 */ \
|
||||
MATRIX_KEY(0x04, 0x02, KEY_D) /* 33 */ \
|
||||
MATRIX_KEY(0x04, 0x03, KEY_F) /* 34 */ \
|
||||
MATRIX_KEY(0x04, 0x05, KEY_K) /* 38 */ \
|
||||
MATRIX_KEY(0x04, 0x06, KEY_J) /* 37 */ \
|
||||
MATRIX_KEY(0x04, 0x08, KEY_SEMICOLON) /* 40 */ \
|
||||
MATRIX_KEY(0x04, 0x09, KEY_L) /* 39 */ \
|
||||
MATRIX_KEY(0x04, 0x0b, KEY_ENTER) /* 43 */ \
|
||||
MATRIX_KEY(0x04, 0x0c, KEY_END) /* 81 (Numpad) */ \
|
||||
\
|
||||
MATRIX_KEY(0x05, 0x01, KEY_1) /* 2 */ \
|
||||
MATRIX_KEY(0x05, 0x02, KEY_COMMA) /* 53 */ \
|
||||
MATRIX_KEY(0x05, 0x03, KEY_DOT) /* 54 */ \
|
||||
MATRIX_KEY(0x05, 0x04, KEY_SLASH) /* 55 */ \
|
||||
MATRIX_KEY(0x05, 0x05, KEY_C) /* 48 */ \
|
||||
MATRIX_KEY(0x05, 0x06, KEY_SPACE) /* 61 */ \
|
||||
MATRIX_KEY(0x05, 0x07, KEY_LEFTSHIFT) /* 44 */ \
|
||||
MATRIX_KEY(0x05, 0x08, KEY_X) /* 47 */ \
|
||||
MATRIX_KEY(0x05, 0x09, KEY_V) /* 49 */ \
|
||||
MATRIX_KEY(0x05, 0x0b, KEY_M) /* 52 */ \
|
||||
MATRIX_KEY(0x05, 0x0c, KEY_PAGEDOWN) /* 86 (Numpad) */ \
|
||||
\
|
||||
MATRIX_KEY(0x06, 0x01, KEY_Z) /* 46 */ \
|
||||
MATRIX_KEY(0x06, 0x02, KEY_3) /* 4 */ \
|
||||
MATRIX_KEY(0x06, 0x03, KEY_4) /* 5 */ \
|
||||
MATRIX_KEY(0x06, 0x04, KEY_2) /* 3 */ \
|
||||
MATRIX_KEY(0x06, 0x05, KEY_8) /* 9 */ \
|
||||
MATRIX_KEY(0x06, 0x06, KEY_0) /* 11 */ \
|
||||
MATRIX_KEY(0x06, 0x08, KEY_7) /* 8 */ \
|
||||
MATRIX_KEY(0x06, 0x09, KEY_9) /* 10 */ \
|
||||
MATRIX_KEY(0x06, 0x0b, KEY_DOWN) /* 84 */ \
|
||||
MATRIX_KEY(0x06, 0x0c, KEY_RIGHT) /* 89 */ \
|
||||
MATRIX_KEY(0x06, 0x0d, KEY_LEFTALT) /* 60 */ \
|
||||
MATRIX_KEY(0x06, 0x0f, KEY_ASSISTANT) /* 128 */ \
|
||||
MATRIX_KEY(0x06, 0x11, KEY_BACKSLASH) /* 42 (JIS, ISO) */ \
|
||||
\
|
||||
MATRIX_KEY(0x07, 0x01, KEY_U) /* 23 */ \
|
||||
MATRIX_KEY(0x07, 0x02, KEY_I) /* 24 */ \
|
||||
MATRIX_KEY(0x07, 0x03, KEY_O) /* 25 */ \
|
||||
MATRIX_KEY(0x07, 0x04, KEY_P) /* 26 */ \
|
||||
MATRIX_KEY(0x07, 0x05, KEY_Q) /* 17 */ \
|
||||
MATRIX_KEY(0x07, 0x06, KEY_W) /* 18 */ \
|
||||
MATRIX_KEY(0x07, 0x07, KEY_RIGHTSHIFT) /* 57 */ \
|
||||
MATRIX_KEY(0x07, 0x08, KEY_E) /* 19 */ \
|
||||
MATRIX_KEY(0x07, 0x09, KEY_R) /* 20 */ \
|
||||
MATRIX_KEY(0x07, 0x0b, KEY_UP) /* 83 */ \
|
||||
MATRIX_KEY(0x07, 0x0c, KEY_LEFT) /* 79 */ \
|
||||
MATRIX_KEY(0x07, 0x11, KEY_102ND) /* 45 (ISO) */
|
||||
|
||||
#endif /* _CROS_EC_KEYBOARD_H */
|
||||
|
|
|
@ -3463,6 +3463,34 @@ union __ec_align_offset1 ec_response_get_next_data_v1 {
|
|||
};
|
||||
BUILD_ASSERT(sizeof(union ec_response_get_next_data_v1) == 16);
|
||||
|
||||
union __ec_align_offset1 ec_response_get_next_data_v3 {
|
||||
uint8_t key_matrix[18];
|
||||
|
||||
/* Unaligned */
|
||||
uint32_t host_event;
|
||||
uint64_t host_event64;
|
||||
|
||||
struct __ec_todo_unpacked {
|
||||
/* For aligning the fifo_info */
|
||||
uint8_t reserved[3];
|
||||
struct ec_response_motion_sense_fifo_info info;
|
||||
} sensor_fifo;
|
||||
|
||||
uint32_t buttons;
|
||||
|
||||
uint32_t switches;
|
||||
|
||||
uint32_t fp_events;
|
||||
|
||||
uint32_t sysrq;
|
||||
|
||||
/* CEC events from enum mkbp_cec_event */
|
||||
uint32_t cec_events;
|
||||
|
||||
uint8_t cec_message[16];
|
||||
};
|
||||
BUILD_ASSERT(sizeof(union ec_response_get_next_data_v3) == 18);
|
||||
|
||||
struct ec_response_get_next_event {
|
||||
uint8_t event_type;
|
||||
/* Followed by event data if any */
|
||||
|
@ -3475,6 +3503,12 @@ struct ec_response_get_next_event_v1 {
|
|||
union ec_response_get_next_data_v1 data;
|
||||
} __ec_align1;
|
||||
|
||||
struct ec_response_get_next_event_v3 {
|
||||
uint8_t event_type;
|
||||
/* Followed by event data if any */
|
||||
union ec_response_get_next_data_v3 data;
|
||||
} __ec_align1;
|
||||
|
||||
/* Bit indices for buttons and switches.*/
|
||||
/* Buttons */
|
||||
#define EC_MKBP_POWER_BUTTON 0
|
||||
|
@ -3809,16 +3843,61 @@ struct ec_params_i2c_write {
|
|||
* discharge the battery.
|
||||
*/
|
||||
#define EC_CMD_CHARGE_CONTROL 0x0096
|
||||
#define EC_VER_CHARGE_CONTROL 1
|
||||
#define EC_VER_CHARGE_CONTROL 3
|
||||
|
||||
enum ec_charge_control_mode {
|
||||
CHARGE_CONTROL_NORMAL = 0,
|
||||
CHARGE_CONTROL_IDLE,
|
||||
CHARGE_CONTROL_DISCHARGE,
|
||||
/* Add no more entry below. */
|
||||
CHARGE_CONTROL_COUNT,
|
||||
};
|
||||
|
||||
#define EC_CHARGE_MODE_TEXT \
|
||||
{ \
|
||||
[CHARGE_CONTROL_NORMAL] = "NORMAL", \
|
||||
[CHARGE_CONTROL_IDLE] = "IDLE", \
|
||||
[CHARGE_CONTROL_DISCHARGE] = "DISCHARGE", \
|
||||
}
|
||||
|
||||
enum ec_charge_control_cmd {
|
||||
EC_CHARGE_CONTROL_CMD_SET = 0,
|
||||
EC_CHARGE_CONTROL_CMD_GET,
|
||||
};
|
||||
|
||||
enum ec_charge_control_flag {
|
||||
EC_CHARGE_CONTROL_FLAG_NO_IDLE = BIT(0),
|
||||
};
|
||||
|
||||
struct ec_params_charge_control {
|
||||
uint32_t mode; /* enum charge_control_mode */
|
||||
uint32_t mode; /* enum charge_control_mode */
|
||||
|
||||
/* Below are the fields added in V2. */
|
||||
uint8_t cmd; /* enum ec_charge_control_cmd. */
|
||||
uint8_t flags; /* enum ec_charge_control_flag (v3+) */
|
||||
/*
|
||||
* Lower and upper thresholds for battery sustainer. This struct isn't
|
||||
* named to avoid tainting foreign projects' name spaces.
|
||||
*
|
||||
* If charge mode is explicitly set (e.g. DISCHARGE), battery sustainer
|
||||
* will be disabled. To disable battery sustainer, set mode=NORMAL,
|
||||
* lower=-1, upper=-1.
|
||||
*/
|
||||
struct {
|
||||
int8_t lower; /* Display SoC in percentage. */
|
||||
int8_t upper; /* Display SoC in percentage. */
|
||||
} sustain_soc;
|
||||
} __ec_align4;
|
||||
|
||||
/* Added in v2 */
|
||||
struct ec_response_charge_control {
|
||||
uint32_t mode; /* enum charge_control_mode */
|
||||
struct { /* Battery sustainer thresholds */
|
||||
int8_t lower;
|
||||
int8_t upper;
|
||||
} sustain_soc;
|
||||
uint8_t flags; /* enum ec_charge_control_flag (v3+) */
|
||||
uint8_t reserved;
|
||||
} __ec_align4;
|
||||
|
||||
/*****************************************************************************/
|
||||
|
|
|
@ -185,7 +185,7 @@ struct cros_ec_device {
|
|||
bool host_sleep_v1;
|
||||
struct blocking_notifier_head event_notifier;
|
||||
|
||||
struct ec_response_get_next_event_v1 event_data;
|
||||
struct ec_response_get_next_event_v3 event_data;
|
||||
int event_size;
|
||||
u32 host_event_wake_mask;
|
||||
u32 last_resume_result;
|
||||
|
@ -261,6 +261,10 @@ int cros_ec_get_sensor_count(struct cros_ec_dev *ec);
|
|||
int cros_ec_cmd(struct cros_ec_device *ec_dev, unsigned int version, int command, const void *outdata,
|
||||
size_t outsize, void *indata, size_t insize);
|
||||
|
||||
int cros_ec_cmd_readmem(struct cros_ec_device *ec_dev, u8 offset, u8 size, void *dest);
|
||||
|
||||
int cros_ec_get_cmd_versions(struct cros_ec_device *ec_dev, u16 cmd);
|
||||
|
||||
/**
|
||||
* cros_ec_get_time_ns() - Return time in ns.
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue