1 // SPDX-License-Identifier: GPL-2.0+
3 * AC driver for 7th-generation Microsoft Surface devices via Surface System
4 * Aggregator Module (SSAM).
6 * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
9 #include <linux/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>
16 #include <linux/surface_aggregator/device.h>
19 /* -- SAM interface. -------------------------------------------------------- */
21 enum sam_event_cid_bat
{
22 SAM_EVENT_CID_BAT_ADP
= 0x17,
25 enum sam_battery_sta
{
26 SAM_BATTERY_STA_OK
= 0x0f,
27 SAM_BATTERY_STA_PRESENT
= 0x10,
30 /* Get battery status (_STA). */
31 SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta
, __le32
, {
32 .target_category
= SSAM_SSH_TC_BAT
,
36 /* Get platform power source for battery (_PSR / DPTF PSRC). */
37 SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc
, __le32
, {
38 .target_category
= SSAM_SSH_TC_BAT
,
43 /* -- Device structures. ---------------------------------------------------- */
45 struct spwr_psy_properties
{
47 struct ssam_event_registry registry
;
50 struct spwr_ac_device
{
51 struct ssam_device
*sdev
;
54 struct power_supply
*psy
;
55 struct power_supply_desc psy_desc
;
57 struct ssam_event_notifier notif
;
59 struct mutex lock
; /* Guards access to state below. */
65 /* -- State management. ----------------------------------------------------- */
67 static int spwr_ac_update_unlocked(struct spwr_ac_device
*ac
)
69 __le32 old
= ac
->state
;
72 lockdep_assert_held(&ac
->lock
);
74 status
= ssam_retry(ssam_bat_get_psrc
, ac
->sdev
, &ac
->state
);
78 return old
!= ac
->state
;
81 static int spwr_ac_update(struct spwr_ac_device
*ac
)
85 mutex_lock(&ac
->lock
);
86 status
= spwr_ac_update_unlocked(ac
);
87 mutex_unlock(&ac
->lock
);
92 static int spwr_ac_recheck(struct spwr_ac_device
*ac
)
96 status
= spwr_ac_update(ac
);
98 power_supply_changed(ac
->psy
);
100 return status
>= 0 ? 0 : status
;
103 static u32
spwr_notify_ac(struct ssam_event_notifier
*nf
, const struct ssam_event
*event
)
105 struct spwr_ac_device
*ac
;
108 ac
= container_of(nf
, struct spwr_ac_device
, notif
);
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
);
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.
118 * While it should be enough to just listen on 1/1, listen everywhere to
119 * make sure we don't miss anything.
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
;
133 /* -- Properties. ----------------------------------------------------------- */
135 static const enum power_supply_property spwr_ac_props
[] = {
136 POWER_SUPPLY_PROP_ONLINE
,
139 static int spwr_ac_get_property(struct power_supply
*psy
, enum power_supply_property psp
,
140 union power_supply_propval
*val
)
142 struct spwr_ac_device
*ac
= power_supply_get_drvdata(psy
);
145 mutex_lock(&ac
->lock
);
147 status
= spwr_ac_update_unlocked(ac
);
152 case POWER_SUPPLY_PROP_ONLINE
:
153 val
->intval
= !!le32_to_cpu(ac
->state
);
162 mutex_unlock(&ac
->lock
);
167 /* -- Device setup. --------------------------------------------------------- */
169 static char *battery_supplied_to
[] = {
174 static void spwr_ac_init(struct spwr_ac_device
*ac
, struct ssam_device
*sdev
,
175 struct ssam_event_registry registry
, const char *name
)
177 mutex_init(&ac
->lock
);
178 strscpy(ac
->name
, name
, sizeof(ac
->name
));
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
;
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
;
197 static int spwr_ac_register(struct spwr_ac_device
*ac
)
199 struct power_supply_config psy_cfg
= {};
203 /* Make sure the device is there and functioning properly. */
204 status
= ssam_retry(ssam_bat_get_sta
, ac
->sdev
, &sta
);
208 if ((le32_to_cpu(sta
) & SAM_BATTERY_STA_OK
) != SAM_BATTERY_STA_OK
)
211 psy_cfg
.drv_data
= ac
;
212 psy_cfg
.supplied_to
= battery_supplied_to
;
213 psy_cfg
.num_supplicants
= ARRAY_SIZE(battery_supplied_to
);
215 ac
->psy
= devm_power_supply_register(&ac
->sdev
->dev
, &ac
->psy_desc
, &psy_cfg
);
217 return PTR_ERR(ac
->psy
);
219 return ssam_device_notifier_register(ac
->sdev
, &ac
->notif
);
223 /* -- Driver setup. --------------------------------------------------------- */
225 static int __maybe_unused
surface_ac_resume(struct device
*dev
)
227 return spwr_ac_recheck(dev_get_drvdata(dev
));
229 static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops
, NULL
, surface_ac_resume
);
231 static int surface_ac_probe(struct ssam_device
*sdev
)
233 const struct spwr_psy_properties
*p
;
234 struct spwr_ac_device
*ac
;
236 p
= ssam_device_get_match_data(sdev
);
240 ac
= devm_kzalloc(&sdev
->dev
, sizeof(*ac
), GFP_KERNEL
);
244 spwr_ac_init(ac
, sdev
, p
->registry
, p
->name
);
245 ssam_device_set_drvdata(sdev
, ac
);
247 return spwr_ac_register(ac
);
250 static void surface_ac_remove(struct ssam_device
*sdev
)
252 struct spwr_ac_device
*ac
= ssam_device_get_drvdata(sdev
);
254 ssam_device_notifier_unregister(sdev
, &ac
->notif
);
257 static const struct spwr_psy_properties spwr_psy_props_adp1
= {
259 .registry
= SSAM_EVENT_REGISTRY_SAM
,
262 static const struct ssam_device_id surface_ac_match
[] = {
263 { SSAM_SDEV(BAT
, SAM
, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1
},
266 MODULE_DEVICE_TABLE(ssam
, surface_ac_match
);
268 static struct ssam_device_driver surface_ac_driver
= {
269 .probe
= surface_ac_probe
,
270 .remove
= surface_ac_remove
,
271 .match_table
= surface_ac_match
,
273 .name
= "surface_ac",
274 .pm
= &surface_ac_pm_ops
,
275 .probe_type
= PROBE_PREFER_ASYNCHRONOUS
,
278 module_ssam_device_driver(surface_ac_driver
);
280 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
281 MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module");
282 MODULE_LICENSE("GPL");