HID: nvidia-shield: Add battery support for Thunderstrike

Use power supply API to expose battery information about connected
Thunderstrike controllers to the system. Provide information on battery
capacity, charge status, charger type, voltage, and temperature.

Signed-off-by: Rahul Rameshbabu <rrameshbabu@nvidia.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
This commit is contained in:
Rahul Rameshbabu 2023-08-07 09:36:19 -07:00 committed by Jiri Kosina
parent cb818a047f
commit 3ab196f882

View File

@ -6,11 +6,15 @@
*/
#include <linux/hid.h>
#include <linux/idr.h>
#include <linux/input-event-codes.h>
#include <linux/input.h>
#include <linux/jiffies.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include "hid-ids.h"
@ -30,6 +34,8 @@ enum {
enum {
SHIELD_FW_VERSION_INITIALIZED = 0,
SHIELD_BOARD_INFO_INITIALIZED,
SHIELD_BATTERY_STATS_INITIALIZED,
SHIELD_CHARGER_STATE_INITIALIZED,
};
enum {
@ -37,6 +43,7 @@ enum {
THUNDERSTRIKE_BOARD_INFO_UPDATE,
THUNDERSTRIKE_HAPTICS_UPDATE,
THUNDERSTRIKE_LED_UPDATE,
THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE,
};
enum {
@ -48,10 +55,46 @@ enum {
enum {
THUNDERSTRIKE_HOSTCMD_ID_FW_VERSION = 1,
THUNDERSTRIKE_HOSTCMD_ID_LED = 6,
THUNDERSTRIKE_HOSTCMD_ID_BATTERY,
THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO = 16,
THUNDERSTRIKE_HOSTCMD_ID_USB_INIT = 53,
THUNDERSTRIKE_HOSTCMD_ID_HAPTICS = 57,
THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT = 58,
THUNDERSTRIKE_HOSTCMD_ID_CHARGER,
};
struct power_supply_dev {
struct power_supply *psy;
struct power_supply_desc desc;
};
struct thunderstrike_psy_prop_values {
int voltage_min;
int voltage_now;
int voltage_avg;
int voltage_boot;
int capacity;
int status;
int charge_type;
int temp;
};
static const enum power_supply_property thunderstrike_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_MIN,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_BOOT,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_MIN,
POWER_SUPPLY_PROP_TEMP_MAX,
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
};
enum thunderstrike_led_state {
@ -60,6 +103,38 @@ enum thunderstrike_led_state {
} __packed;
static_assert(sizeof(enum thunderstrike_led_state) == 1);
struct thunderstrike_hostcmd_battery {
__le16 voltage_avg;
u8 reserved_at_10;
__le16 thermistor;
__le16 voltage_min;
__le16 voltage_boot;
__le16 voltage_now;
u8 capacity;
} __packed;
enum thunderstrike_charger_type {
THUNDERSTRIKE_CHARGER_TYPE_NONE = 0,
THUNDERSTRIKE_CHARGER_TYPE_TRICKLE,
THUNDERSTRIKE_CHARGER_TYPE_NORMAL,
} __packed;
static_assert(sizeof(enum thunderstrike_charger_type) == 1);
enum thunderstrike_charger_state {
THUNDERSTRIKE_CHARGER_STATE_UNKNOWN = 0,
THUNDERSTRIKE_CHARGER_STATE_DISABLED,
THUNDERSTRIKE_CHARGER_STATE_CHARGING,
THUNDERSTRIKE_CHARGER_STATE_FULL,
THUNDERSTRIKE_CHARGER_STATE_FAILED = 8,
} __packed;
static_assert(sizeof(enum thunderstrike_charger_state) == 1);
struct thunderstrike_hostcmd_charger {
u8 connected;
enum thunderstrike_charger_type type;
enum thunderstrike_charger_state state;
} __packed;
struct thunderstrike_hostcmd_board_info {
__le16 revision;
__le16 serial[7];
@ -80,6 +155,8 @@ struct thunderstrike_hostcmd_resp_report {
struct thunderstrike_hostcmd_haptics motors;
__le16 fw_version;
enum thunderstrike_led_state led_state;
struct thunderstrike_hostcmd_battery battery;
struct thunderstrike_hostcmd_charger charger;
u8 payload[30];
} __packed;
} __packed;
@ -109,6 +186,7 @@ static_assert(sizeof(struct thunderstrike_hostcmd_req_report) ==
/* Common struct for shield accessories. */
struct shield_device {
struct hid_device *hdev;
struct power_supply_dev battery_dev;
unsigned long initialized_flags;
const char *codename;
@ -119,9 +197,17 @@ struct shield_device {
} board_info;
};
/*
* Non-trivial to uniquely identify Thunderstrike controllers at initialization
* time. Use an ID allocator to help with this.
*/
static DEFINE_IDA(thunderstrike_ida);
struct thunderstrike {
struct shield_device base;
int id;
/* Sub-devices */
struct input_dev *haptics_dev;
struct led_classdev led_dev;
@ -133,6 +219,9 @@ struct thunderstrike {
spinlock_t haptics_update_lock;
u8 led_state : 1;
enum thunderstrike_led_state led_value;
struct thunderstrike_psy_prop_values psy_stats;
spinlock_t psy_stats_lock;
struct timer_list psy_stats_timer;
struct work_struct hostcmd_req_work;
};
@ -247,6 +336,16 @@ static void thunderstrike_hostcmd_req_work_handler(struct work_struct *work)
thunderstrike_send_hostcmd_request(ts);
}
if (test_and_clear_bit(THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, &ts->update_flags)) {
thunderstrike_hostcmd_req_report_init(
report, THUNDERSTRIKE_HOSTCMD_ID_BATTERY);
thunderstrike_send_hostcmd_request(ts);
thunderstrike_hostcmd_req_report_init(
report, THUNDERSTRIKE_HOSTCMD_ID_CHARGER);
thunderstrike_send_hostcmd_request(ts);
}
if (test_and_clear_bit(THUNDERSTRIKE_BOARD_INFO_UPDATE, &ts->update_flags)) {
thunderstrike_hostcmd_req_report_init(
report, THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO);
@ -352,6 +451,93 @@ static void thunderstrike_led_set_brightness(struct led_classdev *led,
schedule_work(&ts->hostcmd_req_work);
}
static int thunderstrike_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct shield_device *shield_dev = power_supply_get_drvdata(psy);
struct thunderstrike_psy_prop_values prop_values;
struct thunderstrike *ts;
int ret = 0;
ts = container_of(shield_dev, struct thunderstrike, base);
spin_lock(&ts->psy_stats_lock);
prop_values = ts->psy_stats;
spin_unlock(&ts->psy_stats_lock);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = prop_values.status;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = prop_values.charge_type;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
val->intval = prop_values.voltage_min;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = 2900000; /* 2.9 V */
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = 2200000; /* 2.2 V */
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = prop_values.voltage_now;
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
val->intval = prop_values.voltage_avg;
break;
case POWER_SUPPLY_PROP_VOLTAGE_BOOT:
val->intval = prop_values.voltage_boot;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = prop_values.capacity;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = prop_values.temp;
break;
case POWER_SUPPLY_PROP_TEMP_MIN:
val->intval = 0; /* 0 C */
break;
case POWER_SUPPLY_PROP_TEMP_MAX:
val->intval = 400; /* 40 C */
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
val->intval = 15; /* 1.5 C */
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
val->intval = 380; /* 38 C */
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static inline void thunderstrike_request_psy_stats(struct thunderstrike *ts)
{
set_bit(THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, &ts->update_flags);
schedule_work(&ts->hostcmd_req_work);
}
static void thunderstrike_psy_stats_timer_handler(struct timer_list *timer)
{
struct thunderstrike *ts =
container_of(timer, struct thunderstrike, psy_stats_timer);
thunderstrike_request_psy_stats(ts);
/* Query battery statistics from device every five minutes */
mod_timer(timer, jiffies + 300 * HZ);
}
static void
thunderstrike_parse_fw_version_payload(struct shield_device *shield_dev,
__le16 fw_version)
@ -416,13 +602,138 @@ thunderstrike_parse_led_payload(struct shield_device *shield_dev,
hid_dbg(shield_dev->hdev, "Thunderstrike led HOSTCMD response, 0x%02X\n", led_state);
}
static void thunderstrike_parse_battery_payload(
struct shield_device *shield_dev,
struct thunderstrike_hostcmd_battery *battery)
{
struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
u16 hostcmd_voltage_boot = le16_to_cpu(battery->voltage_boot);
u16 hostcmd_voltage_avg = le16_to_cpu(battery->voltage_avg);
u16 hostcmd_voltage_min = le16_to_cpu(battery->voltage_min);
u16 hostcmd_voltage_now = le16_to_cpu(battery->voltage_now);
u16 hostcmd_thermistor = le16_to_cpu(battery->thermistor);
int voltage_boot, voltage_avg, voltage_min, voltage_now;
struct hid_device *hdev = shield_dev->hdev;
u8 capacity = battery->capacity;
int temp;
/* Convert thunderstrike device values to µV and tenths of degree Celsius */
voltage_boot = hostcmd_voltage_boot * 1000;
voltage_avg = hostcmd_voltage_avg * 1000;
voltage_min = hostcmd_voltage_min * 1000;
voltage_now = hostcmd_voltage_now * 1000;
temp = (1378 - (int)hostcmd_thermistor) * 10 / 19;
/* Copy converted values */
spin_lock(&ts->psy_stats_lock);
ts->psy_stats.voltage_boot = voltage_boot;
ts->psy_stats.voltage_avg = voltage_avg;
ts->psy_stats.voltage_min = voltage_min;
ts->psy_stats.voltage_now = voltage_now;
ts->psy_stats.capacity = capacity;
ts->psy_stats.temp = temp;
spin_unlock(&ts->psy_stats_lock);
set_bit(SHIELD_BATTERY_STATS_INITIALIZED, &shield_dev->initialized_flags);
hid_dbg(hdev,
"Thunderstrike battery HOSTCMD response, voltage_avg: %u voltage_now: %u\n",
hostcmd_voltage_avg, hostcmd_voltage_now);
hid_dbg(hdev,
"Thunderstrike battery HOSTCMD response, voltage_boot: %u voltage_min: %u\n",
hostcmd_voltage_boot, hostcmd_voltage_min);
hid_dbg(hdev,
"Thunderstrike battery HOSTCMD response, thermistor: %u\n",
hostcmd_thermistor);
hid_dbg(hdev,
"Thunderstrike battery HOSTCMD response, capacity: %u%%\n",
capacity);
}
static void thunderstrike_parse_charger_payload(
struct shield_device *shield_dev,
struct thunderstrike_hostcmd_charger *charger)
{
struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
int charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
struct hid_device *hdev = shield_dev->hdev;
int status = POWER_SUPPLY_STATUS_UNKNOWN;
switch (charger->type) {
case THUNDERSTRIKE_CHARGER_TYPE_NONE:
charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
case THUNDERSTRIKE_CHARGER_TYPE_TRICKLE:
charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
case THUNDERSTRIKE_CHARGER_TYPE_NORMAL:
charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
break;
default:
hid_warn(hdev, "Unhandled Thunderstrike charger HOSTCMD type, %u\n",
charger->type);
break;
}
switch (charger->state) {
case THUNDERSTRIKE_CHARGER_STATE_UNKNOWN:
status = POWER_SUPPLY_STATUS_UNKNOWN;
break;
case THUNDERSTRIKE_CHARGER_STATE_DISABLED:
/* Indicates charger is disconnected */
break;
case THUNDERSTRIKE_CHARGER_STATE_CHARGING:
status = POWER_SUPPLY_STATUS_CHARGING;
break;
case THUNDERSTRIKE_CHARGER_STATE_FULL:
status = POWER_SUPPLY_STATUS_FULL;
break;
case THUNDERSTRIKE_CHARGER_STATE_FAILED:
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
hid_err(hdev, "Thunderstrike device failed to charge\n");
break;
default:
hid_warn(hdev, "Unhandled Thunderstrike charger HOSTCMD state, %u\n",
charger->state);
break;
}
if (!charger->connected)
status = POWER_SUPPLY_STATUS_DISCHARGING;
spin_lock(&ts->psy_stats_lock);
ts->psy_stats.charge_type = charge_type;
ts->psy_stats.status = status;
spin_unlock(&ts->psy_stats_lock);
set_bit(SHIELD_CHARGER_STATE_INITIALIZED, &shield_dev->initialized_flags);
hid_dbg(hdev,
"Thunderstrike charger HOSTCMD response, connected: %u, type: %u, state: %u\n",
charger->connected, charger->type, charger->state);
}
static inline void thunderstrike_device_init_info(struct shield_device *shield_dev)
{
struct thunderstrike *ts =
container_of(shield_dev, struct thunderstrike, base);
if (!test_bit(SHIELD_FW_VERSION_INITIALIZED, &shield_dev->initialized_flags))
thunderstrike_request_firmware_version(ts);
if (!test_bit(SHIELD_BOARD_INFO_INITIALIZED, &shield_dev->initialized_flags))
thunderstrike_request_board_info(ts);
if (!test_bit(SHIELD_BATTERY_STATS_INITIALIZED, &shield_dev->initialized_flags) ||
!test_bit(SHIELD_CHARGER_STATE_INITIALIZED, &shield_dev->initialized_flags))
thunderstrike_psy_stats_timer_handler(&ts->psy_stats_timer);
}
static int thunderstrike_parse_report(struct shield_device *shield_dev,
struct hid_report *report, u8 *data,
int size)
{
struct thunderstrike_hostcmd_resp_report *hostcmd_resp_report;
struct thunderstrike *ts =
container_of(shield_dev, struct thunderstrike, base);
struct hid_device *hdev = shield_dev->hdev;
switch (report->id) {
@ -445,6 +756,10 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev,
case THUNDERSTRIKE_HOSTCMD_ID_LED:
thunderstrike_parse_led_payload(shield_dev, hostcmd_resp_report->led_state);
break;
case THUNDERSTRIKE_HOSTCMD_ID_BATTERY:
thunderstrike_parse_battery_payload(shield_dev,
&hostcmd_resp_report->battery);
break;
case THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO:
thunderstrike_parse_board_info_payload(
shield_dev, &hostcmd_resp_report->board_info);
@ -453,14 +768,17 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev,
thunderstrike_parse_haptics_payload(
shield_dev, &hostcmd_resp_report->motors);
break;
case THUNDERSTRIKE_HOSTCMD_ID_USB_INIT:
case THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT:
/* May block HOSTCMD requests till received initially */
thunderstrike_request_firmware_version(ts);
thunderstrike_request_board_info(ts);
/* Only HOSTCMD that can be triggered without a request */
return 0;
thunderstrike_device_init_info(shield_dev);
break;
case THUNDERSTRIKE_HOSTCMD_ID_CHARGER:
/* May block HOSTCMD requests till received initially */
thunderstrike_device_init_info(shield_dev);
thunderstrike_parse_charger_payload(
shield_dev, &hostcmd_resp_report->charger);
break;
default:
hid_warn(hdev,
"Unhandled Thunderstrike HOSTCMD id %d\n",
@ -489,6 +807,50 @@ static inline int thunderstrike_led_create(struct thunderstrike *ts)
return led_classdev_register(&ts->base.hdev->dev, led);
}
static inline int thunderstrike_psy_create(struct shield_device *shield_dev)
{
struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
struct power_supply_config psy_cfg = { .drv_data = shield_dev, };
struct hid_device *hdev = shield_dev->hdev;
int ret;
/*
* Set an initial capacity and temperature value to avoid prematurely
* triggering alerts. Will be replaced by values queried from initial
* HOSTCMD requests.
*/
ts->psy_stats.capacity = 100;
ts->psy_stats.temp = 182;
shield_dev->battery_dev.desc.properties = thunderstrike_battery_props;
shield_dev->battery_dev.desc.num_properties =
ARRAY_SIZE(thunderstrike_battery_props);
shield_dev->battery_dev.desc.get_property = thunderstrike_battery_get_property;
shield_dev->battery_dev.desc.type = POWER_SUPPLY_TYPE_BATTERY;
shield_dev->battery_dev.desc.name =
devm_kasprintf(&ts->base.hdev->dev, GFP_KERNEL,
"thunderstrike_%d", ts->id);
shield_dev->battery_dev.psy = power_supply_register(
&hdev->dev, &shield_dev->battery_dev.desc, &psy_cfg);
if (IS_ERR(shield_dev->battery_dev.psy)) {
hid_err(hdev, "Failed to register Thunderstrike battery device\n");
return PTR_ERR(shield_dev->battery_dev.psy);
}
ret = power_supply_powers(shield_dev->battery_dev.psy, &hdev->dev);
if (ret) {
hid_err(hdev, "Failed to associate battery device to Thunderstrike\n");
goto err;
}
return 0;
err:
power_supply_unregister(shield_dev->battery_dev.psy);
return ret;
}
static struct shield_device *thunderstrike_create(struct hid_device *hdev)
{
struct shield_device *shield_dev;
@ -509,27 +871,47 @@ static struct shield_device *thunderstrike_create(struct hid_device *hdev)
shield_dev->codename = "Thunderstrike";
spin_lock_init(&ts->haptics_update_lock);
spin_lock_init(&ts->psy_stats_lock);
INIT_WORK(&ts->hostcmd_req_work, thunderstrike_hostcmd_req_work_handler);
hid_set_drvdata(hdev, shield_dev);
ts->id = ida_alloc(&thunderstrike_ida, GFP_KERNEL);
if (ts->id < 0)
return ERR_PTR(ts->id);
ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect);
if (IS_ERR(ts->haptics_dev))
return ERR_CAST(ts->haptics_dev);
if (IS_ERR(ts->haptics_dev)) {
hid_err(hdev, "Failed to create Thunderstrike haptics instance\n");
ret = PTR_ERR(ts->haptics_dev);
goto err_id;
}
ret = thunderstrike_psy_create(shield_dev);
if (ret) {
hid_err(hdev, "Failed to create Thunderstrike power supply instance\n");
goto err_haptics;
}
ret = thunderstrike_led_create(ts);
if (ret) {
hid_err(hdev, "Failed to create Thunderstrike LED instance\n");
goto err;
goto err_psy;
}
timer_setup(&ts->psy_stats_timer, thunderstrike_psy_stats_timer_handler, 0);
hid_info(hdev, "Registered Thunderstrike controller\n");
return shield_dev;
err:
err_psy:
power_supply_unregister(shield_dev->battery_dev.psy);
err_haptics:
if (ts->haptics_dev)
input_unregister_device(ts->haptics_dev);
return ERR_CAST(ts->haptics_dev);
err_id:
ida_free(&thunderstrike_ida, ts->id);
return ERR_PTR(ret);
}
static int android_input_mapping(struct hid_device *hdev, struct hid_input *hi,
@ -684,8 +1066,7 @@ static int shield_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto err_stop;
}
thunderstrike_request_firmware_version(ts);
thunderstrike_request_board_info(ts);
thunderstrike_device_init_info(shield_dev);
return ret;
@ -705,9 +1086,12 @@ static void shield_remove(struct hid_device *hdev)
ts = container_of(dev, struct thunderstrike, base);
hid_hw_close(hdev);
led_classdev_unregister(&ts->led_dev);
power_supply_unregister(dev->battery_dev.psy);
if (ts->haptics_dev)
input_unregister_device(ts->haptics_dev);
led_classdev_unregister(&ts->led_dev);
ida_free(&thunderstrike_ida, ts->id);
del_timer_sync(&ts->psy_stats_timer);
cancel_work_sync(&ts->hostcmd_req_work);
hid_hw_stop(hdev);
}