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