Commit d30bca44 authored by Roderick Colenbrander's avatar Roderick Colenbrander Committed by Benjamin Tissoires

HID: playstation: add DualSense battery support.

Report DualSense battery status information through power_supply class.
Signed-off-by: default avatarRoderick Colenbrander <roderick.colenbrander@sony.com>
Reviewed-by: default avatarBarnabás Pőcze <pobrn@protonmail.com>
Signed-off-by: default avatarBenjamin Tissoires <benjamin.tissoires@redhat.com>
parent b99dcefd
...@@ -856,6 +856,7 @@ config HID_PLANTRONICS ...@@ -856,6 +856,7 @@ config HID_PLANTRONICS
config HID_PLAYSTATION config HID_PLAYSTATION
tristate "PlayStation HID Driver" tristate "PlayStation HID Driver"
depends on HID depends on HID
select POWER_SUPPLY
help help
Provides support for Sony PS5 controllers including support for Provides support for Sony PS5 controllers including support for
its special functionalities e.g. touchpad, lights and motion its special functionalities e.g. touchpad, lights and motion
......
...@@ -20,6 +20,13 @@ ...@@ -20,6 +20,13 @@
/* Base class for playstation devices. */ /* Base class for playstation devices. */
struct ps_device { struct ps_device {
struct hid_device *hdev; struct hid_device *hdev;
spinlock_t lock;
struct power_supply_desc battery_desc;
struct power_supply *battery;
uint8_t battery_capacity;
int battery_status;
uint8_t mac_address[6]; /* Note: stored in little endian order. */ uint8_t mac_address[6]; /* Note: stored in little endian order. */
int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size); int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size);
...@@ -48,6 +55,11 @@ struct ps_device { ...@@ -48,6 +55,11 @@ struct ps_device {
#define DS_BUTTONS2_PS_HOME BIT(0) #define DS_BUTTONS2_PS_HOME BIT(0)
#define DS_BUTTONS2_TOUCHPAD BIT(1) #define DS_BUTTONS2_TOUCHPAD BIT(1)
/* Status field of DualSense input report. */
#define DS_STATUS_BATTERY_CAPACITY GENMASK(3, 0)
#define DS_STATUS_CHARGING GENMASK(7, 4)
#define DS_STATUS_CHARGING_SHIFT 4
struct dualsense { struct dualsense {
struct ps_device base; struct ps_device base;
struct input_dev *gamepad; struct input_dev *gamepad;
...@@ -140,6 +152,81 @@ static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const ch ...@@ -140,6 +152,81 @@ static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const ch
return input_dev; return input_dev;
} }
static enum power_supply_property ps_power_supply_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_SCOPE,
};
static int ps_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct ps_device *dev = power_supply_get_drvdata(psy);
uint8_t battery_capacity;
int battery_status;
unsigned long flags;
int ret;
spin_lock_irqsave(&dev->lock, flags);
battery_capacity = dev->battery_capacity;
battery_status = dev->battery_status;
spin_unlock_irqrestore(&dev->lock, flags);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = battery_status;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = battery_capacity;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
default:
ret = -EINVAL;
break;
}
return 0;
}
static int ps_device_register_battery(struct ps_device *dev)
{
struct power_supply *battery;
struct power_supply_config battery_cfg = { .drv_data = dev };
int ret;
dev->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
dev->battery_desc.properties = ps_power_supply_props;
dev->battery_desc.num_properties = ARRAY_SIZE(ps_power_supply_props);
dev->battery_desc.get_property = ps_battery_get_property;
dev->battery_desc.name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL,
"ps-controller-battery-%pMR", dev->mac_address);
if (!dev->battery_desc.name)
return -ENOMEM;
battery = devm_power_supply_register(&dev->hdev->dev, &dev->battery_desc, &battery_cfg);
if (IS_ERR(battery)) {
ret = PTR_ERR(battery);
hid_err(dev->hdev, "Unable to register battery device: %d\n", ret);
return ret;
}
dev->battery = battery;
ret = power_supply_powers(dev->battery, &dev->hdev->dev);
if (ret) {
hid_err(dev->hdev, "Unable to activate battery device: %d\n", ret);
return ret;
}
return 0;
}
static struct input_dev *ps_gamepad_create(struct hid_device *hdev) static struct input_dev *ps_gamepad_create(struct hid_device *hdev)
{ {
struct input_dev *gamepad; struct input_dev *gamepad;
...@@ -223,7 +310,9 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r ...@@ -223,7 +310,9 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
struct hid_device *hdev = ps_dev->hdev; struct hid_device *hdev = ps_dev->hdev;
struct dualsense *ds = container_of(ps_dev, struct dualsense, base); struct dualsense *ds = container_of(ps_dev, struct dualsense, base);
struct dualsense_input_report *ds_report; struct dualsense_input_report *ds_report;
uint8_t value; uint8_t battery_data, battery_capacity, charging_status, value;
int battery_status;
unsigned long flags;
/* /*
* DualSense in USB uses the full HID report for reportID 1, but * DualSense in USB uses the full HID report for reportID 1, but
...@@ -266,12 +355,49 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r ...@@ -266,12 +355,49 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME); input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME);
input_sync(ds->gamepad); input_sync(ds->gamepad);
battery_data = ds_report->status & DS_STATUS_BATTERY_CAPACITY;
charging_status = (ds_report->status & DS_STATUS_CHARGING) >> DS_STATUS_CHARGING_SHIFT;
switch (charging_status) {
case 0x0:
/*
* Each unit of battery data corresponds to 10%
* 0 = 0-9%, 1 = 10-19%, .. and 10 = 100%
*/
battery_capacity = min(battery_data * 10 + 5, 100);
battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case 0x1:
battery_capacity = min(battery_data * 10 + 5, 100);
battery_status = POWER_SUPPLY_STATUS_CHARGING;
break;
case 0x2:
battery_capacity = 100;
battery_status = POWER_SUPPLY_STATUS_FULL;
break;
case 0xa: /* voltage or temperature out of range */
case 0xb: /* temperature error */
battery_capacity = 0;
battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case 0xf: /* charging error */
default:
battery_capacity = 0;
battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
}
spin_lock_irqsave(&ps_dev->lock, flags);
ps_dev->battery_capacity = battery_capacity;
ps_dev->battery_status = battery_status;
spin_unlock_irqrestore(&ps_dev->lock, flags);
return 0; return 0;
} }
static struct ps_device *dualsense_create(struct hid_device *hdev) static struct ps_device *dualsense_create(struct hid_device *hdev)
{ {
struct dualsense *ds; struct dualsense *ds;
struct ps_device *ps_dev;
int ret; int ret;
ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL); ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL);
...@@ -284,8 +410,12 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) ...@@ -284,8 +410,12 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
*/ */
hdev->version |= HID_PLAYSTATION_VERSION_PATCH; hdev->version |= HID_PLAYSTATION_VERSION_PATCH;
ds->base.hdev = hdev; ps_dev = &ds->base;
ds->base.parse_report = dualsense_parse_report; ps_dev->hdev = hdev;
spin_lock_init(&ps_dev->lock);
ps_dev->battery_capacity = 100; /* initial value until parse_report. */
ps_dev->battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
ps_dev->parse_report = dualsense_parse_report;
hid_set_drvdata(hdev, ds); hid_set_drvdata(hdev, ds);
ret = dualsense_get_mac_address(ds); ret = dualsense_get_mac_address(ds);
...@@ -301,6 +431,10 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) ...@@ -301,6 +431,10 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
goto err; goto err;
} }
ret = ps_device_register_battery(ps_dev);
if (ret)
goto err;
return &ds->base; return &ds->base;
err: err:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment