162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * AC driver for 7th-generation Microsoft Surface devices via Surface System 462306a36Sopenharmony_ci * 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/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/mutex.h> 1362306a36Sopenharmony_ci#include <linux/power_supply.h> 1462306a36Sopenharmony_ci#include <linux/types.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/surface_aggregator/device.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* -- SAM interface. -------------------------------------------------------- */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cienum sam_event_cid_bat { 2262306a36Sopenharmony_ci SAM_EVENT_CID_BAT_ADP = 0x17, 2362306a36Sopenharmony_ci}; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cienum sam_battery_sta { 2662306a36Sopenharmony_ci SAM_BATTERY_STA_OK = 0x0f, 2762306a36Sopenharmony_ci SAM_BATTERY_STA_PRESENT = 0x10, 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* Get battery status (_STA). */ 3162306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { 3262306a36Sopenharmony_ci .target_category = SSAM_SSH_TC_BAT, 3362306a36Sopenharmony_ci .command_id = 0x01, 3462306a36Sopenharmony_ci}); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* Get platform power source for battery (_PSR / DPTF PSRC). */ 3762306a36Sopenharmony_ciSSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { 3862306a36Sopenharmony_ci .target_category = SSAM_SSH_TC_BAT, 3962306a36Sopenharmony_ci .command_id = 0x0d, 4062306a36Sopenharmony_ci}); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/* -- Device structures. ---------------------------------------------------- */ 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistruct spwr_psy_properties { 4662306a36Sopenharmony_ci const char *name; 4762306a36Sopenharmony_ci struct ssam_event_registry registry; 4862306a36Sopenharmony_ci}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistruct spwr_ac_device { 5162306a36Sopenharmony_ci struct ssam_device *sdev; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci char name[32]; 5462306a36Sopenharmony_ci struct power_supply *psy; 5562306a36Sopenharmony_ci struct power_supply_desc psy_desc; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci struct ssam_event_notifier notif; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci struct mutex lock; /* Guards access to state below. */ 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci __le32 state; 6262306a36Sopenharmony_ci}; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci/* -- State management. ----------------------------------------------------- */ 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic int spwr_ac_update_unlocked(struct spwr_ac_device *ac) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci __le32 old = ac->state; 7062306a36Sopenharmony_ci int status; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci lockdep_assert_held(&ac->lock); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state); 7562306a36Sopenharmony_ci if (status < 0) 7662306a36Sopenharmony_ci return status; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci return old != ac->state; 7962306a36Sopenharmony_ci} 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic int spwr_ac_update(struct spwr_ac_device *ac) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci int status; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci mutex_lock(&ac->lock); 8662306a36Sopenharmony_ci status = spwr_ac_update_unlocked(ac); 8762306a36Sopenharmony_ci mutex_unlock(&ac->lock); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return status; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic int spwr_ac_recheck(struct spwr_ac_device *ac) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci int status; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci status = spwr_ac_update(ac); 9762306a36Sopenharmony_ci if (status > 0) 9862306a36Sopenharmony_ci power_supply_changed(ac->psy); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci return status >= 0 ? 0 : status; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci struct spwr_ac_device *ac; 10662306a36Sopenharmony_ci int status; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci ac = container_of(nf, struct spwr_ac_device, notif); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", 11162306a36Sopenharmony_ci event->command_id, event->instance_id, event->target_id); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* 11462306a36Sopenharmony_ci * Allow events of all targets/instances here. Global adapter status 11562306a36Sopenharmony_ci * seems to be handled via target=1 and instance=1, but events are 11662306a36Sopenharmony_ci * reported on all targets/instances in use. 11762306a36Sopenharmony_ci * 11862306a36Sopenharmony_ci * While it should be enough to just listen on 1/1, listen everywhere to 11962306a36Sopenharmony_ci * make sure we don't miss anything. 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci switch (event->command_id) { 12362306a36Sopenharmony_ci case SAM_EVENT_CID_BAT_ADP: 12462306a36Sopenharmony_ci status = spwr_ac_recheck(ac); 12562306a36Sopenharmony_ci return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci default: 12862306a36Sopenharmony_ci return 0; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci/* -- Properties. ----------------------------------------------------------- */ 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic const enum power_supply_property spwr_ac_props[] = { 13662306a36Sopenharmony_ci POWER_SUPPLY_PROP_ONLINE, 13762306a36Sopenharmony_ci}; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, 14062306a36Sopenharmony_ci union power_supply_propval *val) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct spwr_ac_device *ac = power_supply_get_drvdata(psy); 14362306a36Sopenharmony_ci int status; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci mutex_lock(&ac->lock); 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci status = spwr_ac_update_unlocked(ac); 14862306a36Sopenharmony_ci if (status) 14962306a36Sopenharmony_ci goto out; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci switch (psp) { 15262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_ONLINE: 15362306a36Sopenharmony_ci val->intval = !!le32_to_cpu(ac->state); 15462306a36Sopenharmony_ci break; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci default: 15762306a36Sopenharmony_ci status = -EINVAL; 15862306a36Sopenharmony_ci goto out; 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ciout: 16262306a36Sopenharmony_ci mutex_unlock(&ac->lock); 16362306a36Sopenharmony_ci return status; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci/* -- Device setup. --------------------------------------------------------- */ 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic char *battery_supplied_to[] = { 17062306a36Sopenharmony_ci "BAT1", 17162306a36Sopenharmony_ci "BAT2", 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, 17562306a36Sopenharmony_ci struct ssam_event_registry registry, const char *name) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci mutex_init(&ac->lock); 17862306a36Sopenharmony_ci strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci ac->sdev = sdev; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci ac->notif.base.priority = 1; 18362306a36Sopenharmony_ci ac->notif.base.fn = spwr_notify_ac; 18462306a36Sopenharmony_ci ac->notif.event.reg = registry; 18562306a36Sopenharmony_ci ac->notif.event.id.target_category = sdev->uid.category; 18662306a36Sopenharmony_ci ac->notif.event.id.instance = 0; 18762306a36Sopenharmony_ci ac->notif.event.mask = SSAM_EVENT_MASK_NONE; 18862306a36Sopenharmony_ci ac->notif.event.flags = SSAM_EVENT_SEQUENCED; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci ac->psy_desc.name = ac->name; 19162306a36Sopenharmony_ci ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; 19262306a36Sopenharmony_ci ac->psy_desc.properties = spwr_ac_props; 19362306a36Sopenharmony_ci ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); 19462306a36Sopenharmony_ci ac->psy_desc.get_property = spwr_ac_get_property; 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic int spwr_ac_register(struct spwr_ac_device *ac) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci struct power_supply_config psy_cfg = {}; 20062306a36Sopenharmony_ci __le32 sta; 20162306a36Sopenharmony_ci int status; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci /* Make sure the device is there and functioning properly. */ 20462306a36Sopenharmony_ci status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta); 20562306a36Sopenharmony_ci if (status) 20662306a36Sopenharmony_ci return status; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) 20962306a36Sopenharmony_ci return -ENODEV; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci psy_cfg.drv_data = ac; 21262306a36Sopenharmony_ci psy_cfg.supplied_to = battery_supplied_to; 21362306a36Sopenharmony_ci psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); 21662306a36Sopenharmony_ci if (IS_ERR(ac->psy)) 21762306a36Sopenharmony_ci return PTR_ERR(ac->psy); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci return ssam_device_notifier_register(ac->sdev, &ac->notif); 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci/* -- Driver setup. --------------------------------------------------------- */ 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic int __maybe_unused surface_ac_resume(struct device *dev) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci return spwr_ac_recheck(dev_get_drvdata(dev)); 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic int surface_ac_probe(struct ssam_device *sdev) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci const struct spwr_psy_properties *p; 23462306a36Sopenharmony_ci struct spwr_ac_device *ac; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci p = ssam_device_get_match_data(sdev); 23762306a36Sopenharmony_ci if (!p) 23862306a36Sopenharmony_ci return -ENODEV; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); 24162306a36Sopenharmony_ci if (!ac) 24262306a36Sopenharmony_ci return -ENOMEM; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci spwr_ac_init(ac, sdev, p->registry, p->name); 24562306a36Sopenharmony_ci ssam_device_set_drvdata(sdev, ac); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci return spwr_ac_register(ac); 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void surface_ac_remove(struct ssam_device *sdev) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci ssam_device_notifier_unregister(sdev, &ac->notif); 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistatic const struct spwr_psy_properties spwr_psy_props_adp1 = { 25862306a36Sopenharmony_ci .name = "ADP1", 25962306a36Sopenharmony_ci .registry = SSAM_EVENT_REGISTRY_SAM, 26062306a36Sopenharmony_ci}; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic const struct ssam_device_id surface_ac_match[] = { 26362306a36Sopenharmony_ci { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, 26462306a36Sopenharmony_ci { }, 26562306a36Sopenharmony_ci}; 26662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(ssam, surface_ac_match); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_cistatic struct ssam_device_driver surface_ac_driver = { 26962306a36Sopenharmony_ci .probe = surface_ac_probe, 27062306a36Sopenharmony_ci .remove = surface_ac_remove, 27162306a36Sopenharmony_ci .match_table = surface_ac_match, 27262306a36Sopenharmony_ci .driver = { 27362306a36Sopenharmony_ci .name = "surface_ac", 27462306a36Sopenharmony_ci .pm = &surface_ac_pm_ops, 27562306a36Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 27662306a36Sopenharmony_ci }, 27762306a36Sopenharmony_ci}; 27862306a36Sopenharmony_cimodule_ssam_device_driver(surface_ac_driver); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ciMODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 28162306a36Sopenharmony_ciMODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); 28262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 283