162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Battery driver for 7th-generation Microsoft Surface devices via Surface
462306a36Sopenharmony_ci * System Aggregator Module (SSAM).
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <asm/unaligned.h>
1062306a36Sopenharmony_ci#include <linux/jiffies.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/mutex.h>
1462306a36Sopenharmony_ci#include <linux/power_supply.h>
1562306a36Sopenharmony_ci#include <linux/sysfs.h>
1662306a36Sopenharmony_ci#include <linux/types.h>
1762306a36Sopenharmony_ci#include <linux/workqueue.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/surface_aggregator/device.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/* -- SAM interface. -------------------------------------------------------- */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cienum sam_event_cid_bat {
2562306a36Sopenharmony_ci	SAM_EVENT_CID_BAT_BIX         = 0x15,
2662306a36Sopenharmony_ci	SAM_EVENT_CID_BAT_BST         = 0x16,
2762306a36Sopenharmony_ci	SAM_EVENT_CID_BAT_ADP         = 0x17,
2862306a36Sopenharmony_ci	SAM_EVENT_CID_BAT_PROT        = 0x18,
2962306a36Sopenharmony_ci	SAM_EVENT_CID_BAT_DPTF        = 0x53,
3062306a36Sopenharmony_ci};
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cienum sam_battery_sta {
3362306a36Sopenharmony_ci	SAM_BATTERY_STA_OK            = 0x0f,
3462306a36Sopenharmony_ci	SAM_BATTERY_STA_PRESENT	      = 0x10,
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cienum sam_battery_state {
3862306a36Sopenharmony_ci	SAM_BATTERY_STATE_DISCHARGING = BIT(0),
3962306a36Sopenharmony_ci	SAM_BATTERY_STATE_CHARGING    = BIT(1),
4062306a36Sopenharmony_ci	SAM_BATTERY_STATE_CRITICAL    = BIT(2),
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cienum sam_battery_power_unit {
4462306a36Sopenharmony_ci	SAM_BATTERY_POWER_UNIT_mW     = 0,
4562306a36Sopenharmony_ci	SAM_BATTERY_POWER_UNIT_mA     = 1,
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/* Equivalent to data returned in ACPI _BIX method, revision 0. */
4962306a36Sopenharmony_cistruct spwr_bix {
5062306a36Sopenharmony_ci	u8  revision;
5162306a36Sopenharmony_ci	__le32 power_unit;
5262306a36Sopenharmony_ci	__le32 design_cap;
5362306a36Sopenharmony_ci	__le32 last_full_charge_cap;
5462306a36Sopenharmony_ci	__le32 technology;
5562306a36Sopenharmony_ci	__le32 design_voltage;
5662306a36Sopenharmony_ci	__le32 design_cap_warn;
5762306a36Sopenharmony_ci	__le32 design_cap_low;
5862306a36Sopenharmony_ci	__le32 cycle_count;
5962306a36Sopenharmony_ci	__le32 measurement_accuracy;
6062306a36Sopenharmony_ci	__le32 max_sampling_time;
6162306a36Sopenharmony_ci	__le32 min_sampling_time;
6262306a36Sopenharmony_ci	__le32 max_avg_interval;
6362306a36Sopenharmony_ci	__le32 min_avg_interval;
6462306a36Sopenharmony_ci	__le32 bat_cap_granularity_1;
6562306a36Sopenharmony_ci	__le32 bat_cap_granularity_2;
6662306a36Sopenharmony_ci	__u8 model[21];
6762306a36Sopenharmony_ci	__u8 serial[11];
6862306a36Sopenharmony_ci	__u8 type[5];
6962306a36Sopenharmony_ci	__u8 oem_info[21];
7062306a36Sopenharmony_ci} __packed;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic_assert(sizeof(struct spwr_bix) == 119);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/* Equivalent to data returned in ACPI _BST method. */
7562306a36Sopenharmony_cistruct spwr_bst {
7662306a36Sopenharmony_ci	__le32 state;
7762306a36Sopenharmony_ci	__le32 present_rate;
7862306a36Sopenharmony_ci	__le32 remaining_cap;
7962306a36Sopenharmony_ci	__le32 present_voltage;
8062306a36Sopenharmony_ci} __packed;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic_assert(sizeof(struct spwr_bst) == 16);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci#define SPWR_BIX_REVISION		0
8562306a36Sopenharmony_ci#define SPWR_BATTERY_VALUE_UNKNOWN	0xffffffff
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci/* Get battery status (_STA) */
8862306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
8962306a36Sopenharmony_ci	.target_category = SSAM_SSH_TC_BAT,
9062306a36Sopenharmony_ci	.command_id      = 0x01,
9162306a36Sopenharmony_ci});
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci/* Get battery static information (_BIX). */
9462306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, {
9562306a36Sopenharmony_ci	.target_category = SSAM_SSH_TC_BAT,
9662306a36Sopenharmony_ci	.command_id      = 0x02,
9762306a36Sopenharmony_ci});
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci/* Get battery dynamic information (_BST). */
10062306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, {
10162306a36Sopenharmony_ci	.target_category = SSAM_SSH_TC_BAT,
10262306a36Sopenharmony_ci	.command_id      = 0x03,
10362306a36Sopenharmony_ci});
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/* Set battery trip point (_BTP). */
10662306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, {
10762306a36Sopenharmony_ci	.target_category = SSAM_SSH_TC_BAT,
10862306a36Sopenharmony_ci	.command_id      = 0x04,
10962306a36Sopenharmony_ci});
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci/* -- Device structures. ---------------------------------------------------- */
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistruct spwr_psy_properties {
11562306a36Sopenharmony_ci	const char *name;
11662306a36Sopenharmony_ci	struct ssam_event_registry registry;
11762306a36Sopenharmony_ci};
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistruct spwr_battery_device {
12062306a36Sopenharmony_ci	struct ssam_device *sdev;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	char name[32];
12362306a36Sopenharmony_ci	struct power_supply *psy;
12462306a36Sopenharmony_ci	struct power_supply_desc psy_desc;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	struct delayed_work update_work;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	struct ssam_event_notifier notif;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	struct mutex lock;  /* Guards access to state data below. */
13162306a36Sopenharmony_ci	unsigned long timestamp;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	__le32 sta;
13462306a36Sopenharmony_ci	struct spwr_bix bix;
13562306a36Sopenharmony_ci	struct spwr_bst bst;
13662306a36Sopenharmony_ci	u32 alarm;
13762306a36Sopenharmony_ci};
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci/* -- Module parameters. ---------------------------------------------------- */
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic unsigned int cache_time = 1000;
14362306a36Sopenharmony_cimodule_param(cache_time, uint, 0644);
14462306a36Sopenharmony_ciMODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]");
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci/* -- State management. ----------------------------------------------------- */
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci/*
15062306a36Sopenharmony_ci * Delay for battery update quirk. See spwr_external_power_changed() below
15162306a36Sopenharmony_ci * for more details.
15262306a36Sopenharmony_ci */
15362306a36Sopenharmony_ci#define SPWR_AC_BAT_UPDATE_DELAY	msecs_to_jiffies(5000)
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic bool spwr_battery_present(struct spwr_battery_device *bat)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT;
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic int spwr_battery_load_sta(struct spwr_battery_device *bat)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta);
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic int spwr_battery_load_bix(struct spwr_battery_device *bat)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	int status;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	if (!spwr_battery_present(bat))
17662306a36Sopenharmony_ci		return 0;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/* Enforce NULL terminated strings in case anything goes wrong... */
18162306a36Sopenharmony_ci	bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0;
18262306a36Sopenharmony_ci	bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0;
18362306a36Sopenharmony_ci	bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0;
18462306a36Sopenharmony_ci	bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	return status;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic int spwr_battery_load_bst(struct spwr_battery_device *bat)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (!spwr_battery_present(bat))
19462306a36Sopenharmony_ci		return 0;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst);
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	__le32 value_le = cpu_to_le32(value);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	bat->alarm = value;
20662306a36Sopenharmony_ci	return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le);
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time);
21262306a36Sopenharmony_ci	int status;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline))
21762306a36Sopenharmony_ci		return 0;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	status = spwr_battery_load_sta(bat);
22062306a36Sopenharmony_ci	if (status)
22162306a36Sopenharmony_ci		return status;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	status = spwr_battery_load_bst(bat);
22462306a36Sopenharmony_ci	if (status)
22562306a36Sopenharmony_ci		return status;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	bat->timestamp = jiffies;
22862306a36Sopenharmony_ci	return 0;
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached)
23262306a36Sopenharmony_ci{
23362306a36Sopenharmony_ci	int status;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	mutex_lock(&bat->lock);
23662306a36Sopenharmony_ci	status = spwr_battery_update_bst_unlocked(bat, cached);
23762306a36Sopenharmony_ci	mutex_unlock(&bat->lock);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	return status;
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	int status;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	status = spwr_battery_load_sta(bat);
24962306a36Sopenharmony_ci	if (status)
25062306a36Sopenharmony_ci		return status;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	status = spwr_battery_load_bix(bat);
25362306a36Sopenharmony_ci	if (status)
25462306a36Sopenharmony_ci		return status;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	status = spwr_battery_load_bst(bat);
25762306a36Sopenharmony_ci	if (status)
25862306a36Sopenharmony_ci		return status;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	if (bat->bix.revision != SPWR_BIX_REVISION)
26162306a36Sopenharmony_ci		dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	bat->timestamp = jiffies;
26462306a36Sopenharmony_ci	return 0;
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat)
26862306a36Sopenharmony_ci{
26962306a36Sopenharmony_ci	u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap);
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
27462306a36Sopenharmony_ci		full_cap = get_unaligned_le32(&bat->bix.design_cap);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	return full_cap;
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_cistatic bool spwr_battery_is_full(struct spwr_battery_device *bat)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	u32 state = get_unaligned_le32(&bat->bst.state);
28262306a36Sopenharmony_ci	u32 full_cap = sprw_battery_get_full_cap_safe(bat);
28362306a36Sopenharmony_ci	u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 &&
28862306a36Sopenharmony_ci		remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN &&
28962306a36Sopenharmony_ci		remaining_cap >= full_cap &&
29062306a36Sopenharmony_ci		state == 0;
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic int spwr_battery_recheck_full(struct spwr_battery_device *bat)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	bool present;
29662306a36Sopenharmony_ci	u32 unit;
29762306a36Sopenharmony_ci	int status;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	mutex_lock(&bat->lock);
30062306a36Sopenharmony_ci	unit = get_unaligned_le32(&bat->bix.power_unit);
30162306a36Sopenharmony_ci	present = spwr_battery_present(bat);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	status = spwr_battery_update_bix_unlocked(bat);
30462306a36Sopenharmony_ci	if (status)
30562306a36Sopenharmony_ci		goto out;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	/* If battery has been attached, (re-)initialize alarm. */
30862306a36Sopenharmony_ci	if (!present && spwr_battery_present(bat)) {
30962306a36Sopenharmony_ci		u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci		status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
31262306a36Sopenharmony_ci		if (status)
31362306a36Sopenharmony_ci			goto out;
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	/*
31762306a36Sopenharmony_ci	 * Warn if the unit has changed. This is something we genuinely don't
31862306a36Sopenharmony_ci	 * expect to happen, so make this a big warning. If it does, we'll
31962306a36Sopenharmony_ci	 * need to add support for it.
32062306a36Sopenharmony_ci	 */
32162306a36Sopenharmony_ci	WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit));
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ciout:
32462306a36Sopenharmony_ci	mutex_unlock(&bat->lock);
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	if (!status)
32762306a36Sopenharmony_ci		power_supply_changed(bat->psy);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	return status;
33062306a36Sopenharmony_ci}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_cistatic int spwr_battery_recheck_status(struct spwr_battery_device *bat)
33362306a36Sopenharmony_ci{
33462306a36Sopenharmony_ci	int status;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	status = spwr_battery_update_bst(bat, false);
33762306a36Sopenharmony_ci	if (!status)
33862306a36Sopenharmony_ci		power_supply_changed(bat->psy);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	return status;
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_cistatic u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event)
34462306a36Sopenharmony_ci{
34562306a36Sopenharmony_ci	struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
34662306a36Sopenharmony_ci	int status;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	/*
34962306a36Sopenharmony_ci	 * We cannot use strict matching when registering the notifier as the
35062306a36Sopenharmony_ci	 * EC expects us to register it against instance ID 0. Strict matching
35162306a36Sopenharmony_ci	 * would thus drop events, as those may have non-zero instance IDs in
35262306a36Sopenharmony_ci	 * this subsystem. So we need to check the instance ID of the event
35362306a36Sopenharmony_ci	 * here manually.
35462306a36Sopenharmony_ci	 */
35562306a36Sopenharmony_ci	if (event->instance_id != bat->sdev->uid.instance)
35662306a36Sopenharmony_ci		return 0;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
35962306a36Sopenharmony_ci		event->command_id, event->instance_id, event->target_id);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	switch (event->command_id) {
36262306a36Sopenharmony_ci	case SAM_EVENT_CID_BAT_BIX:
36362306a36Sopenharmony_ci		status = spwr_battery_recheck_full(bat);
36462306a36Sopenharmony_ci		break;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	case SAM_EVENT_CID_BAT_BST:
36762306a36Sopenharmony_ci		status = spwr_battery_recheck_status(bat);
36862306a36Sopenharmony_ci		break;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	case SAM_EVENT_CID_BAT_PROT:
37162306a36Sopenharmony_ci		/*
37262306a36Sopenharmony_ci		 * TODO: Implement support for battery protection status change
37362306a36Sopenharmony_ci		 *       event.
37462306a36Sopenharmony_ci		 */
37562306a36Sopenharmony_ci		status = 0;
37662306a36Sopenharmony_ci		break;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	case SAM_EVENT_CID_BAT_DPTF:
37962306a36Sopenharmony_ci		/*
38062306a36Sopenharmony_ci		 * TODO: Implement support for DPTF event.
38162306a36Sopenharmony_ci		 */
38262306a36Sopenharmony_ci		status = 0;
38362306a36Sopenharmony_ci		break;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	default:
38662306a36Sopenharmony_ci		return 0;
38762306a36Sopenharmony_ci	}
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
39062306a36Sopenharmony_ci}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_cistatic void spwr_battery_update_bst_workfn(struct work_struct *work)
39362306a36Sopenharmony_ci{
39462306a36Sopenharmony_ci	struct delayed_work *dwork = to_delayed_work(work);
39562306a36Sopenharmony_ci	struct spwr_battery_device *bat;
39662306a36Sopenharmony_ci	int status;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	bat = container_of(dwork, struct spwr_battery_device, update_work);
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	status = spwr_battery_update_bst(bat, false);
40162306a36Sopenharmony_ci	if (status) {
40262306a36Sopenharmony_ci		dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status);
40362306a36Sopenharmony_ci		return;
40462306a36Sopenharmony_ci	}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	power_supply_changed(bat->psy);
40762306a36Sopenharmony_ci}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_cistatic void spwr_external_power_changed(struct power_supply *psy)
41062306a36Sopenharmony_ci{
41162306a36Sopenharmony_ci	struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	/*
41462306a36Sopenharmony_ci	 * Handle battery update quirk: When the battery is fully charged (or
41562306a36Sopenharmony_ci	 * charged up to the limit imposed by the UEFI battery limit) and the
41662306a36Sopenharmony_ci	 * adapter is plugged in or removed, the EC does not send a separate
41762306a36Sopenharmony_ci	 * event for the state (charging/discharging) change. Furthermore it
41862306a36Sopenharmony_ci	 * may take some time until the state is updated on the battery.
41962306a36Sopenharmony_ci	 * Schedule an update to solve this.
42062306a36Sopenharmony_ci	 */
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY);
42362306a36Sopenharmony_ci}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci/* -- Properties. ----------------------------------------------------------- */
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_cistatic const enum power_supply_property spwr_battery_props_chg[] = {
42962306a36Sopenharmony_ci	POWER_SUPPLY_PROP_STATUS,
43062306a36Sopenharmony_ci	POWER_SUPPLY_PROP_PRESENT,
43162306a36Sopenharmony_ci	POWER_SUPPLY_PROP_TECHNOLOGY,
43262306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CYCLE_COUNT,
43362306a36Sopenharmony_ci	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
43462306a36Sopenharmony_ci	POWER_SUPPLY_PROP_VOLTAGE_NOW,
43562306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CURRENT_NOW,
43662306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
43762306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CHARGE_FULL,
43862306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CHARGE_NOW,
43962306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CAPACITY,
44062306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
44162306a36Sopenharmony_ci	POWER_SUPPLY_PROP_MODEL_NAME,
44262306a36Sopenharmony_ci	POWER_SUPPLY_PROP_MANUFACTURER,
44362306a36Sopenharmony_ci	POWER_SUPPLY_PROP_SERIAL_NUMBER,
44462306a36Sopenharmony_ci};
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_cistatic const enum power_supply_property spwr_battery_props_eng[] = {
44762306a36Sopenharmony_ci	POWER_SUPPLY_PROP_STATUS,
44862306a36Sopenharmony_ci	POWER_SUPPLY_PROP_PRESENT,
44962306a36Sopenharmony_ci	POWER_SUPPLY_PROP_TECHNOLOGY,
45062306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CYCLE_COUNT,
45162306a36Sopenharmony_ci	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
45262306a36Sopenharmony_ci	POWER_SUPPLY_PROP_VOLTAGE_NOW,
45362306a36Sopenharmony_ci	POWER_SUPPLY_PROP_POWER_NOW,
45462306a36Sopenharmony_ci	POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
45562306a36Sopenharmony_ci	POWER_SUPPLY_PROP_ENERGY_FULL,
45662306a36Sopenharmony_ci	POWER_SUPPLY_PROP_ENERGY_NOW,
45762306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CAPACITY,
45862306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
45962306a36Sopenharmony_ci	POWER_SUPPLY_PROP_MODEL_NAME,
46062306a36Sopenharmony_ci	POWER_SUPPLY_PROP_MANUFACTURER,
46162306a36Sopenharmony_ci	POWER_SUPPLY_PROP_SERIAL_NUMBER,
46262306a36Sopenharmony_ci};
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_cistatic int spwr_battery_prop_status(struct spwr_battery_device *bat)
46562306a36Sopenharmony_ci{
46662306a36Sopenharmony_ci	u32 state = get_unaligned_le32(&bat->bst.state);
46762306a36Sopenharmony_ci	u32 present_rate = get_unaligned_le32(&bat->bst.present_rate);
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	if (state & SAM_BATTERY_STATE_DISCHARGING)
47262306a36Sopenharmony_ci		return POWER_SUPPLY_STATUS_DISCHARGING;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	if (state & SAM_BATTERY_STATE_CHARGING)
47562306a36Sopenharmony_ci		return POWER_SUPPLY_STATUS_CHARGING;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	if (spwr_battery_is_full(bat))
47862306a36Sopenharmony_ci		return POWER_SUPPLY_STATUS_FULL;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	if (present_rate == 0)
48162306a36Sopenharmony_ci		return POWER_SUPPLY_STATUS_NOT_CHARGING;
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	return POWER_SUPPLY_STATUS_UNKNOWN;
48462306a36Sopenharmony_ci}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_cistatic int spwr_battery_prop_technology(struct spwr_battery_device *bat)
48762306a36Sopenharmony_ci{
48862306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	if (!strcasecmp("NiCd", bat->bix.type))
49162306a36Sopenharmony_ci		return POWER_SUPPLY_TECHNOLOGY_NiCd;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	if (!strcasecmp("NiMH", bat->bix.type))
49462306a36Sopenharmony_ci		return POWER_SUPPLY_TECHNOLOGY_NiMH;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	if (!strcasecmp("LION", bat->bix.type))
49762306a36Sopenharmony_ci		return POWER_SUPPLY_TECHNOLOGY_LION;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	if (!strncasecmp("LI-ION", bat->bix.type, 6))
50062306a36Sopenharmony_ci		return POWER_SUPPLY_TECHNOLOGY_LION;
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	if (!strcasecmp("LiP", bat->bix.type))
50362306a36Sopenharmony_ci		return POWER_SUPPLY_TECHNOLOGY_LIPO;
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	return POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_cistatic int spwr_battery_prop_capacity(struct spwr_battery_device *bat)
50962306a36Sopenharmony_ci{
51062306a36Sopenharmony_ci	u32 full_cap = sprw_battery_get_full_cap_safe(bat);
51162306a36Sopenharmony_ci	u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
51662306a36Sopenharmony_ci		return -ENODATA;
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN)
51962306a36Sopenharmony_ci		return -ENODATA;
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	return remaining_cap * 100 / full_cap;
52262306a36Sopenharmony_ci}
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_cistatic int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat)
52562306a36Sopenharmony_ci{
52662306a36Sopenharmony_ci	u32 state = get_unaligned_le32(&bat->bst.state);
52762306a36Sopenharmony_ci	u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	lockdep_assert_held(&bat->lock);
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	if (state & SAM_BATTERY_STATE_CRITICAL)
53262306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	if (spwr_battery_is_full(bat))
53562306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	if (remaining_cap <= bat->alarm)
53862306a36Sopenharmony_ci		return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
54162306a36Sopenharmony_ci}
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_cistatic int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp,
54462306a36Sopenharmony_ci				     union power_supply_propval *val)
54562306a36Sopenharmony_ci{
54662306a36Sopenharmony_ci	struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
54762306a36Sopenharmony_ci	u32 value;
54862306a36Sopenharmony_ci	int status;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	mutex_lock(&bat->lock);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	status = spwr_battery_update_bst_unlocked(bat, true);
55362306a36Sopenharmony_ci	if (status)
55462306a36Sopenharmony_ci		goto out;
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	/* Abort if battery is not present. */
55762306a36Sopenharmony_ci	if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) {
55862306a36Sopenharmony_ci		status = -ENODEV;
55962306a36Sopenharmony_ci		goto out;
56062306a36Sopenharmony_ci	}
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci	switch (psp) {
56362306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_STATUS:
56462306a36Sopenharmony_ci		val->intval = spwr_battery_prop_status(bat);
56562306a36Sopenharmony_ci		break;
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_PRESENT:
56862306a36Sopenharmony_ci		val->intval = spwr_battery_present(bat);
56962306a36Sopenharmony_ci		break;
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_TECHNOLOGY:
57262306a36Sopenharmony_ci		val->intval = spwr_battery_prop_technology(bat);
57362306a36Sopenharmony_ci		break;
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CYCLE_COUNT:
57662306a36Sopenharmony_ci		value = get_unaligned_le32(&bat->bix.cycle_count);
57762306a36Sopenharmony_ci		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
57862306a36Sopenharmony_ci			val->intval = value;
57962306a36Sopenharmony_ci		else
58062306a36Sopenharmony_ci			status = -ENODATA;
58162306a36Sopenharmony_ci		break;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
58462306a36Sopenharmony_ci		value = get_unaligned_le32(&bat->bix.design_voltage);
58562306a36Sopenharmony_ci		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
58662306a36Sopenharmony_ci			val->intval = value * 1000;
58762306a36Sopenharmony_ci		else
58862306a36Sopenharmony_ci			status = -ENODATA;
58962306a36Sopenharmony_ci		break;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
59262306a36Sopenharmony_ci		value = get_unaligned_le32(&bat->bst.present_voltage);
59362306a36Sopenharmony_ci		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
59462306a36Sopenharmony_ci			val->intval = value * 1000;
59562306a36Sopenharmony_ci		else
59662306a36Sopenharmony_ci			status = -ENODATA;
59762306a36Sopenharmony_ci		break;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CURRENT_NOW:
60062306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_POWER_NOW:
60162306a36Sopenharmony_ci		value = get_unaligned_le32(&bat->bst.present_rate);
60262306a36Sopenharmony_ci		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
60362306a36Sopenharmony_ci			val->intval = value * 1000;
60462306a36Sopenharmony_ci		else
60562306a36Sopenharmony_ci			status = -ENODATA;
60662306a36Sopenharmony_ci		break;
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
60962306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
61062306a36Sopenharmony_ci		value = get_unaligned_le32(&bat->bix.design_cap);
61162306a36Sopenharmony_ci		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
61262306a36Sopenharmony_ci			val->intval = value * 1000;
61362306a36Sopenharmony_ci		else
61462306a36Sopenharmony_ci			status = -ENODATA;
61562306a36Sopenharmony_ci		break;
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CHARGE_FULL:
61862306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_ENERGY_FULL:
61962306a36Sopenharmony_ci		value = get_unaligned_le32(&bat->bix.last_full_charge_cap);
62062306a36Sopenharmony_ci		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
62162306a36Sopenharmony_ci			val->intval = value * 1000;
62262306a36Sopenharmony_ci		else
62362306a36Sopenharmony_ci			status = -ENODATA;
62462306a36Sopenharmony_ci		break;
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CHARGE_NOW:
62762306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_ENERGY_NOW:
62862306a36Sopenharmony_ci		value = get_unaligned_le32(&bat->bst.remaining_cap);
62962306a36Sopenharmony_ci		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
63062306a36Sopenharmony_ci			val->intval = value * 1000;
63162306a36Sopenharmony_ci		else
63262306a36Sopenharmony_ci			status = -ENODATA;
63362306a36Sopenharmony_ci		break;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CAPACITY:
63662306a36Sopenharmony_ci		val->intval = spwr_battery_prop_capacity(bat);
63762306a36Sopenharmony_ci		break;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
64062306a36Sopenharmony_ci		val->intval = spwr_battery_prop_capacity_level(bat);
64162306a36Sopenharmony_ci		break;
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_MODEL_NAME:
64462306a36Sopenharmony_ci		val->strval = bat->bix.model;
64562306a36Sopenharmony_ci		break;
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_MANUFACTURER:
64862306a36Sopenharmony_ci		val->strval = bat->bix.oem_info;
64962306a36Sopenharmony_ci		break;
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_SERIAL_NUMBER:
65262306a36Sopenharmony_ci		val->strval = bat->bix.serial;
65362306a36Sopenharmony_ci		break;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	default:
65662306a36Sopenharmony_ci		status = -EINVAL;
65762306a36Sopenharmony_ci		break;
65862306a36Sopenharmony_ci	}
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ciout:
66162306a36Sopenharmony_ci	mutex_unlock(&bat->lock);
66262306a36Sopenharmony_ci	return status;
66362306a36Sopenharmony_ci}
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci/* -- Alarm attribute. ------------------------------------------------------ */
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_cistatic ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf)
66962306a36Sopenharmony_ci{
67062306a36Sopenharmony_ci	struct power_supply *psy = dev_get_drvdata(dev);
67162306a36Sopenharmony_ci	struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
67262306a36Sopenharmony_ci	int status;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	mutex_lock(&bat->lock);
67562306a36Sopenharmony_ci	status = sysfs_emit(buf, "%d\n", bat->alarm * 1000);
67662306a36Sopenharmony_ci	mutex_unlock(&bat->lock);
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	return status;
67962306a36Sopenharmony_ci}
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_cistatic ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf,
68262306a36Sopenharmony_ci			   size_t count)
68362306a36Sopenharmony_ci{
68462306a36Sopenharmony_ci	struct power_supply *psy = dev_get_drvdata(dev);
68562306a36Sopenharmony_ci	struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
68662306a36Sopenharmony_ci	unsigned long value;
68762306a36Sopenharmony_ci	int status;
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	status = kstrtoul(buf, 0, &value);
69062306a36Sopenharmony_ci	if (status)
69162306a36Sopenharmony_ci		return status;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	mutex_lock(&bat->lock);
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	if (!spwr_battery_present(bat)) {
69662306a36Sopenharmony_ci		mutex_unlock(&bat->lock);
69762306a36Sopenharmony_ci		return -ENODEV;
69862306a36Sopenharmony_ci	}
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	status = spwr_battery_set_alarm_unlocked(bat, value / 1000);
70162306a36Sopenharmony_ci	if (status) {
70262306a36Sopenharmony_ci		mutex_unlock(&bat->lock);
70362306a36Sopenharmony_ci		return status;
70462306a36Sopenharmony_ci	}
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	mutex_unlock(&bat->lock);
70762306a36Sopenharmony_ci	return count;
70862306a36Sopenharmony_ci}
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_cistatic DEVICE_ATTR_RW(alarm);
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_cistatic struct attribute *spwr_battery_attrs[] = {
71362306a36Sopenharmony_ci	&dev_attr_alarm.attr,
71462306a36Sopenharmony_ci	NULL,
71562306a36Sopenharmony_ci};
71662306a36Sopenharmony_ciATTRIBUTE_GROUPS(spwr_battery);
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ci/* -- Device setup. --------------------------------------------------------- */
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_cistatic void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev,
72262306a36Sopenharmony_ci			      struct ssam_event_registry registry, const char *name)
72362306a36Sopenharmony_ci{
72462306a36Sopenharmony_ci	mutex_init(&bat->lock);
72562306a36Sopenharmony_ci	strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1);
72662306a36Sopenharmony_ci
72762306a36Sopenharmony_ci	bat->sdev = sdev;
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci	bat->notif.base.priority = 1;
73062306a36Sopenharmony_ci	bat->notif.base.fn = spwr_notify_bat;
73162306a36Sopenharmony_ci	bat->notif.event.reg = registry;
73262306a36Sopenharmony_ci	bat->notif.event.id.target_category = sdev->uid.category;
73362306a36Sopenharmony_ci	bat->notif.event.id.instance = 0;	/* need to register with instance 0 */
73462306a36Sopenharmony_ci	bat->notif.event.mask = SSAM_EVENT_MASK_TARGET;
73562306a36Sopenharmony_ci	bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
73662306a36Sopenharmony_ci
73762306a36Sopenharmony_ci	bat->psy_desc.name = bat->name;
73862306a36Sopenharmony_ci	bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
73962306a36Sopenharmony_ci	bat->psy_desc.get_property = spwr_battery_get_property;
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_ci	INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn);
74262306a36Sopenharmony_ci}
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_cistatic int spwr_battery_register(struct spwr_battery_device *bat)
74562306a36Sopenharmony_ci{
74662306a36Sopenharmony_ci	struct power_supply_config psy_cfg = {};
74762306a36Sopenharmony_ci	__le32 sta;
74862306a36Sopenharmony_ci	int status;
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_ci	/* Make sure the device is there and functioning properly. */
75162306a36Sopenharmony_ci	status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta);
75262306a36Sopenharmony_ci	if (status)
75362306a36Sopenharmony_ci		return status;
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_ci	if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
75662306a36Sopenharmony_ci		return -ENODEV;
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	/* Satisfy lockdep although we are in an exclusive context here. */
75962306a36Sopenharmony_ci	mutex_lock(&bat->lock);
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ci	status = spwr_battery_update_bix_unlocked(bat);
76262306a36Sopenharmony_ci	if (status) {
76362306a36Sopenharmony_ci		mutex_unlock(&bat->lock);
76462306a36Sopenharmony_ci		return status;
76562306a36Sopenharmony_ci	}
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci	if (spwr_battery_present(bat)) {
76862306a36Sopenharmony_ci		u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci		status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
77162306a36Sopenharmony_ci		if (status) {
77262306a36Sopenharmony_ci			mutex_unlock(&bat->lock);
77362306a36Sopenharmony_ci			return status;
77462306a36Sopenharmony_ci		}
77562306a36Sopenharmony_ci	}
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	mutex_unlock(&bat->lock);
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	bat->psy_desc.external_power_changed = spwr_external_power_changed;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	switch (get_unaligned_le32(&bat->bix.power_unit)) {
78262306a36Sopenharmony_ci	case SAM_BATTERY_POWER_UNIT_mW:
78362306a36Sopenharmony_ci		bat->psy_desc.properties = spwr_battery_props_eng;
78462306a36Sopenharmony_ci		bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng);
78562306a36Sopenharmony_ci		break;
78662306a36Sopenharmony_ci
78762306a36Sopenharmony_ci	case SAM_BATTERY_POWER_UNIT_mA:
78862306a36Sopenharmony_ci		bat->psy_desc.properties = spwr_battery_props_chg;
78962306a36Sopenharmony_ci		bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg);
79062306a36Sopenharmony_ci		break;
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	default:
79362306a36Sopenharmony_ci		dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n",
79462306a36Sopenharmony_ci			get_unaligned_le32(&bat->bix.power_unit));
79562306a36Sopenharmony_ci		return -EINVAL;
79662306a36Sopenharmony_ci	}
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	psy_cfg.drv_data = bat;
79962306a36Sopenharmony_ci	psy_cfg.attr_grp = spwr_battery_groups;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci	bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg);
80262306a36Sopenharmony_ci	if (IS_ERR(bat->psy))
80362306a36Sopenharmony_ci		return PTR_ERR(bat->psy);
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_ci	return ssam_device_notifier_register(bat->sdev, &bat->notif);
80662306a36Sopenharmony_ci}
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci
80962306a36Sopenharmony_ci/* -- Driver setup. --------------------------------------------------------- */
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_cistatic int __maybe_unused surface_battery_resume(struct device *dev)
81262306a36Sopenharmony_ci{
81362306a36Sopenharmony_ci	return spwr_battery_recheck_full(dev_get_drvdata(dev));
81462306a36Sopenharmony_ci}
81562306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_cistatic int surface_battery_probe(struct ssam_device *sdev)
81862306a36Sopenharmony_ci{
81962306a36Sopenharmony_ci	const struct spwr_psy_properties *p;
82062306a36Sopenharmony_ci	struct spwr_battery_device *bat;
82162306a36Sopenharmony_ci
82262306a36Sopenharmony_ci	p = ssam_device_get_match_data(sdev);
82362306a36Sopenharmony_ci	if (!p)
82462306a36Sopenharmony_ci		return -ENODEV;
82562306a36Sopenharmony_ci
82662306a36Sopenharmony_ci	bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL);
82762306a36Sopenharmony_ci	if (!bat)
82862306a36Sopenharmony_ci		return -ENOMEM;
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci	spwr_battery_init(bat, sdev, p->registry, p->name);
83162306a36Sopenharmony_ci	ssam_device_set_drvdata(sdev, bat);
83262306a36Sopenharmony_ci
83362306a36Sopenharmony_ci	return spwr_battery_register(bat);
83462306a36Sopenharmony_ci}
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_cistatic void surface_battery_remove(struct ssam_device *sdev)
83762306a36Sopenharmony_ci{
83862306a36Sopenharmony_ci	struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev);
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	ssam_device_notifier_unregister(sdev, &bat->notif);
84162306a36Sopenharmony_ci	cancel_delayed_work_sync(&bat->update_work);
84262306a36Sopenharmony_ci}
84362306a36Sopenharmony_ci
84462306a36Sopenharmony_cistatic const struct spwr_psy_properties spwr_psy_props_bat1 = {
84562306a36Sopenharmony_ci	.name = "BAT1",
84662306a36Sopenharmony_ci	.registry = SSAM_EVENT_REGISTRY_SAM,
84762306a36Sopenharmony_ci};
84862306a36Sopenharmony_ci
84962306a36Sopenharmony_cistatic const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = {
85062306a36Sopenharmony_ci	.name = "BAT2",
85162306a36Sopenharmony_ci	.registry = SSAM_EVENT_REGISTRY_KIP,
85262306a36Sopenharmony_ci};
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_cistatic const struct ssam_device_id surface_battery_match[] = {
85562306a36Sopenharmony_ci	{ SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1     },
85662306a36Sopenharmony_ci	{ SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 },
85762306a36Sopenharmony_ci	{ },
85862306a36Sopenharmony_ci};
85962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(ssam, surface_battery_match);
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_cistatic struct ssam_device_driver surface_battery_driver = {
86262306a36Sopenharmony_ci	.probe = surface_battery_probe,
86362306a36Sopenharmony_ci	.remove = surface_battery_remove,
86462306a36Sopenharmony_ci	.match_table = surface_battery_match,
86562306a36Sopenharmony_ci	.driver = {
86662306a36Sopenharmony_ci		.name = "surface_battery",
86762306a36Sopenharmony_ci		.pm = &surface_battery_pm_ops,
86862306a36Sopenharmony_ci		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
86962306a36Sopenharmony_ci	},
87062306a36Sopenharmony_ci};
87162306a36Sopenharmony_cimodule_ssam_device_driver(surface_battery_driver);
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ciMODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
87462306a36Sopenharmony_ciMODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module");
87562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
876