Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * AC driver for 7th-generation Microsoft Surface devices via Surface System
0004  * Aggregator Module (SSAM).
0005  *
0006  * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
0007  */
0008 
0009 #include <asm/unaligned.h>
0010 #include <linux/kernel.h>
0011 #include <linux/module.h>
0012 #include <linux/mutex.h>
0013 #include <linux/power_supply.h>
0014 #include <linux/types.h>
0015 
0016 #include <linux/surface_aggregator/device.h>
0017 
0018 
0019 /* -- SAM interface. -------------------------------------------------------- */
0020 
0021 enum sam_event_cid_bat {
0022     SAM_EVENT_CID_BAT_ADP   = 0x17,
0023 };
0024 
0025 enum sam_battery_sta {
0026     SAM_BATTERY_STA_OK      = 0x0f,
0027     SAM_BATTERY_STA_PRESENT = 0x10,
0028 };
0029 
0030 /* Get battery status (_STA). */
0031 SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
0032     .target_category = SSAM_SSH_TC_BAT,
0033     .command_id      = 0x01,
0034 });
0035 
0036 /* Get platform power source for battery (_PSR / DPTF PSRC). */
0037 SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, {
0038     .target_category = SSAM_SSH_TC_BAT,
0039     .command_id      = 0x0d,
0040 });
0041 
0042 
0043 /* -- Device structures. ---------------------------------------------------- */
0044 
0045 struct spwr_psy_properties {
0046     const char *name;
0047     struct ssam_event_registry registry;
0048 };
0049 
0050 struct spwr_ac_device {
0051     struct ssam_device *sdev;
0052 
0053     char name[32];
0054     struct power_supply *psy;
0055     struct power_supply_desc psy_desc;
0056 
0057     struct ssam_event_notifier notif;
0058 
0059     struct mutex lock;  /* Guards access to state below. */
0060 
0061     __le32 state;
0062 };
0063 
0064 
0065 /* -- State management. ----------------------------------------------------- */
0066 
0067 static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
0068 {
0069     __le32 old = ac->state;
0070     int status;
0071 
0072     lockdep_assert_held(&ac->lock);
0073 
0074     status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state);
0075     if (status < 0)
0076         return status;
0077 
0078     return old != ac->state;
0079 }
0080 
0081 static int spwr_ac_update(struct spwr_ac_device *ac)
0082 {
0083     int status;
0084 
0085     mutex_lock(&ac->lock);
0086     status = spwr_ac_update_unlocked(ac);
0087     mutex_unlock(&ac->lock);
0088 
0089     return status;
0090 }
0091 
0092 static int spwr_ac_recheck(struct spwr_ac_device *ac)
0093 {
0094     int status;
0095 
0096     status = spwr_ac_update(ac);
0097     if (status > 0)
0098         power_supply_changed(ac->psy);
0099 
0100     return status >= 0 ? 0 : status;
0101 }
0102 
0103 static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event)
0104 {
0105     struct spwr_ac_device *ac;
0106     int status;
0107 
0108     ac = container_of(nf, struct spwr_ac_device, notif);
0109 
0110     dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
0111         event->command_id, event->instance_id, event->target_id);
0112 
0113     /*
0114      * Allow events of all targets/instances here. Global adapter status
0115      * seems to be handled via target=1 and instance=1, but events are
0116      * reported on all targets/instances in use.
0117      *
0118      * While it should be enough to just listen on 1/1, listen everywhere to
0119      * make sure we don't miss anything.
0120      */
0121 
0122     switch (event->command_id) {
0123     case SAM_EVENT_CID_BAT_ADP:
0124         status = spwr_ac_recheck(ac);
0125         return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
0126 
0127     default:
0128         return 0;
0129     }
0130 }
0131 
0132 
0133 /* -- Properties. ----------------------------------------------------------- */
0134 
0135 static const enum power_supply_property spwr_ac_props[] = {
0136     POWER_SUPPLY_PROP_ONLINE,
0137 };
0138 
0139 static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp,
0140                 union power_supply_propval *val)
0141 {
0142     struct spwr_ac_device *ac = power_supply_get_drvdata(psy);
0143     int status;
0144 
0145     mutex_lock(&ac->lock);
0146 
0147     status = spwr_ac_update_unlocked(ac);
0148     if (status)
0149         goto out;
0150 
0151     switch (psp) {
0152     case POWER_SUPPLY_PROP_ONLINE:
0153         val->intval = !!le32_to_cpu(ac->state);
0154         break;
0155 
0156     default:
0157         status = -EINVAL;
0158         goto out;
0159     }
0160 
0161 out:
0162     mutex_unlock(&ac->lock);
0163     return status;
0164 }
0165 
0166 
0167 /* -- Device setup. --------------------------------------------------------- */
0168 
0169 static char *battery_supplied_to[] = {
0170     "BAT1",
0171     "BAT2",
0172 };
0173 
0174 static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev,
0175              struct ssam_event_registry registry, const char *name)
0176 {
0177     mutex_init(&ac->lock);
0178     strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1);
0179 
0180     ac->sdev = sdev;
0181 
0182     ac->notif.base.priority = 1;
0183     ac->notif.base.fn = spwr_notify_ac;
0184     ac->notif.event.reg = registry;
0185     ac->notif.event.id.target_category = sdev->uid.category;
0186     ac->notif.event.id.instance = 0;
0187     ac->notif.event.mask = SSAM_EVENT_MASK_NONE;
0188     ac->notif.event.flags = SSAM_EVENT_SEQUENCED;
0189 
0190     ac->psy_desc.name = ac->name;
0191     ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
0192     ac->psy_desc.properties = spwr_ac_props;
0193     ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props);
0194     ac->psy_desc.get_property = spwr_ac_get_property;
0195 }
0196 
0197 static int spwr_ac_register(struct spwr_ac_device *ac)
0198 {
0199     struct power_supply_config psy_cfg = {};
0200     __le32 sta;
0201     int status;
0202 
0203     /* Make sure the device is there and functioning properly. */
0204     status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta);
0205     if (status)
0206         return status;
0207 
0208     if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
0209         return -ENODEV;
0210 
0211     psy_cfg.drv_data = ac;
0212     psy_cfg.supplied_to = battery_supplied_to;
0213     psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to);
0214 
0215     ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg);
0216     if (IS_ERR(ac->psy))
0217         return PTR_ERR(ac->psy);
0218 
0219     return ssam_device_notifier_register(ac->sdev, &ac->notif);
0220 }
0221 
0222 
0223 /* -- Driver setup. --------------------------------------------------------- */
0224 
0225 static int __maybe_unused surface_ac_resume(struct device *dev)
0226 {
0227     return spwr_ac_recheck(dev_get_drvdata(dev));
0228 }
0229 static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
0230 
0231 static int surface_ac_probe(struct ssam_device *sdev)
0232 {
0233     const struct spwr_psy_properties *p;
0234     struct spwr_ac_device *ac;
0235 
0236     p = ssam_device_get_match_data(sdev);
0237     if (!p)
0238         return -ENODEV;
0239 
0240     ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL);
0241     if (!ac)
0242         return -ENOMEM;
0243 
0244     spwr_ac_init(ac, sdev, p->registry, p->name);
0245     ssam_device_set_drvdata(sdev, ac);
0246 
0247     return spwr_ac_register(ac);
0248 }
0249 
0250 static void surface_ac_remove(struct ssam_device *sdev)
0251 {
0252     struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev);
0253 
0254     ssam_device_notifier_unregister(sdev, &ac->notif);
0255 }
0256 
0257 static const struct spwr_psy_properties spwr_psy_props_adp1 = {
0258     .name = "ADP1",
0259     .registry = SSAM_EVENT_REGISTRY_SAM,
0260 };
0261 
0262 static const struct ssam_device_id surface_ac_match[] = {
0263     { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 },
0264     { },
0265 };
0266 MODULE_DEVICE_TABLE(ssam, surface_ac_match);
0267 
0268 static struct ssam_device_driver surface_ac_driver = {
0269     .probe = surface_ac_probe,
0270     .remove = surface_ac_remove,
0271     .match_table = surface_ac_match,
0272     .driver = {
0273         .name = "surface_ac",
0274         .pm = &surface_ac_pm_ops,
0275         .probe_type = PROBE_PREFER_ASYNCHRONOUS,
0276     },
0277 };
0278 module_ssam_device_driver(surface_ac_driver);
0279 
0280 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
0281 MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module");
0282 MODULE_LICENSE("GPL");