cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

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");