surface_charger.c (6827B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * AC driver for 7th-generation Microsoft Surface devices via Surface System 4 * Aggregator Module (SSAM). 5 * 6 * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> 7 */ 8 9#include <asm/unaligned.h> 10#include <linux/kernel.h> 11#include <linux/module.h> 12#include <linux/mutex.h> 13#include <linux/power_supply.h> 14#include <linux/types.h> 15 16#include <linux/surface_aggregator/device.h> 17 18 19/* -- SAM interface. -------------------------------------------------------- */ 20 21enum sam_event_cid_bat { 22 SAM_EVENT_CID_BAT_ADP = 0x17, 23}; 24 25enum sam_battery_sta { 26 SAM_BATTERY_STA_OK = 0x0f, 27 SAM_BATTERY_STA_PRESENT = 0x10, 28}; 29 30/* Get battery status (_STA). */ 31SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { 32 .target_category = SSAM_SSH_TC_BAT, 33 .command_id = 0x01, 34}); 35 36/* Get platform power source for battery (_PSR / DPTF PSRC). */ 37SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { 38 .target_category = SSAM_SSH_TC_BAT, 39 .command_id = 0x0d, 40}); 41 42 43/* -- Device structures. ---------------------------------------------------- */ 44 45struct spwr_psy_properties { 46 const char *name; 47 struct ssam_event_registry registry; 48}; 49 50struct spwr_ac_device { 51 struct ssam_device *sdev; 52 53 char name[32]; 54 struct power_supply *psy; 55 struct power_supply_desc psy_desc; 56 57 struct ssam_event_notifier notif; 58 59 struct mutex lock; /* Guards access to state below. */ 60 61 __le32 state; 62}; 63 64 65/* -- State management. ----------------------------------------------------- */ 66 67static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) 68{ 69 __le32 old = ac->state; 70 int status; 71 72 lockdep_assert_held(&ac->lock); 73 74 status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state); 75 if (status < 0) 76 return status; 77 78 return old != ac->state; 79} 80 81static int spwr_ac_update(struct spwr_ac_device *ac) 82{ 83 int status; 84 85 mutex_lock(&ac->lock); 86 status = spwr_ac_update_unlocked(ac); 87 mutex_unlock(&ac->lock); 88 89 return status; 90} 91 92static int spwr_ac_recheck(struct spwr_ac_device *ac) 93{ 94 int status; 95 96 status = spwr_ac_update(ac); 97 if (status > 0) 98 power_supply_changed(ac->psy); 99 100 return status >= 0 ? 0 : status; 101} 102 103static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event) 104{ 105 struct spwr_ac_device *ac; 106 int status; 107 108 ac = container_of(nf, struct spwr_ac_device, notif); 109 110 dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", 111 event->command_id, event->instance_id, event->target_id); 112 113 /* 114 * Allow events of all targets/instances here. Global adapter status 115 * seems to be handled via target=1 and instance=1, but events are 116 * reported on all targets/instances in use. 117 * 118 * While it should be enough to just listen on 1/1, listen everywhere to 119 * make sure we don't miss anything. 120 */ 121 122 switch (event->command_id) { 123 case SAM_EVENT_CID_BAT_ADP: 124 status = spwr_ac_recheck(ac); 125 return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; 126 127 default: 128 return 0; 129 } 130} 131 132 133/* -- Properties. ----------------------------------------------------------- */ 134 135static const enum power_supply_property spwr_ac_props[] = { 136 POWER_SUPPLY_PROP_ONLINE, 137}; 138 139static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, 140 union power_supply_propval *val) 141{ 142 struct spwr_ac_device *ac = power_supply_get_drvdata(psy); 143 int status; 144 145 mutex_lock(&ac->lock); 146 147 status = spwr_ac_update_unlocked(ac); 148 if (status) 149 goto out; 150 151 switch (psp) { 152 case POWER_SUPPLY_PROP_ONLINE: 153 val->intval = !!le32_to_cpu(ac->state); 154 break; 155 156 default: 157 status = -EINVAL; 158 goto out; 159 } 160 161out: 162 mutex_unlock(&ac->lock); 163 return status; 164} 165 166 167/* -- Device setup. --------------------------------------------------------- */ 168 169static char *battery_supplied_to[] = { 170 "BAT1", 171 "BAT2", 172}; 173 174static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, 175 struct ssam_event_registry registry, const char *name) 176{ 177 mutex_init(&ac->lock); 178 strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); 179 180 ac->sdev = sdev; 181 182 ac->notif.base.priority = 1; 183 ac->notif.base.fn = spwr_notify_ac; 184 ac->notif.event.reg = registry; 185 ac->notif.event.id.target_category = sdev->uid.category; 186 ac->notif.event.id.instance = 0; 187 ac->notif.event.mask = SSAM_EVENT_MASK_NONE; 188 ac->notif.event.flags = SSAM_EVENT_SEQUENCED; 189 190 ac->psy_desc.name = ac->name; 191 ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; 192 ac->psy_desc.properties = spwr_ac_props; 193 ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); 194 ac->psy_desc.get_property = spwr_ac_get_property; 195} 196 197static int spwr_ac_register(struct spwr_ac_device *ac) 198{ 199 struct power_supply_config psy_cfg = {}; 200 __le32 sta; 201 int status; 202 203 /* Make sure the device is there and functioning properly. */ 204 status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta); 205 if (status) 206 return status; 207 208 if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) 209 return -ENODEV; 210 211 psy_cfg.drv_data = ac; 212 psy_cfg.supplied_to = battery_supplied_to; 213 psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); 214 215 ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); 216 if (IS_ERR(ac->psy)) 217 return PTR_ERR(ac->psy); 218 219 return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); 220} 221 222 223/* -- Driver setup. --------------------------------------------------------- */ 224 225static int __maybe_unused surface_ac_resume(struct device *dev) 226{ 227 return spwr_ac_recheck(dev_get_drvdata(dev)); 228} 229static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); 230 231static int surface_ac_probe(struct ssam_device *sdev) 232{ 233 const struct spwr_psy_properties *p; 234 struct spwr_ac_device *ac; 235 236 p = ssam_device_get_match_data(sdev); 237 if (!p) 238 return -ENODEV; 239 240 ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); 241 if (!ac) 242 return -ENOMEM; 243 244 spwr_ac_init(ac, sdev, p->registry, p->name); 245 ssam_device_set_drvdata(sdev, ac); 246 247 return spwr_ac_register(ac); 248} 249 250static void surface_ac_remove(struct ssam_device *sdev) 251{ 252 struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); 253 254 ssam_notifier_unregister(sdev->ctrl, &ac->notif); 255} 256 257static const struct spwr_psy_properties spwr_psy_props_adp1 = { 258 .name = "ADP1", 259 .registry = SSAM_EVENT_REGISTRY_SAM, 260}; 261 262static const struct ssam_device_id surface_ac_match[] = { 263 { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, 264 { }, 265}; 266MODULE_DEVICE_TABLE(ssam, surface_ac_match); 267 268static struct ssam_device_driver surface_ac_driver = { 269 .probe = surface_ac_probe, 270 .remove = surface_ac_remove, 271 .match_table = surface_ac_match, 272 .driver = { 273 .name = "surface_ac", 274 .pm = &surface_ac_pm_ops, 275 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 276 }, 277}; 278module_ssam_device_driver(surface_ac_driver); 279 280MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 281MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); 282MODULE_LICENSE("GPL");