162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Fast-charge control for Apple "MFi" devices
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2019 Bastien Nocera <hadess@hadess.net>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci/* Standard include files */
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/power_supply.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/usb.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ciMODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
1562306a36Sopenharmony_ciMODULE_DESCRIPTION("Fast-charge control for Apple \"MFi\" devices");
1662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define TRICKLE_CURRENT_MA		0
1962306a36Sopenharmony_ci#define FAST_CURRENT_MA			2500
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define APPLE_VENDOR_ID			0x05ac	/* Apple */
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* The product ID is defined as starting with 0x12nn, as per the
2462306a36Sopenharmony_ci * "Choosing an Apple Device USB Configuration" section in
2562306a36Sopenharmony_ci * release R9 (2012) of the "MFi Accessory Hardware Specification"
2662306a36Sopenharmony_ci *
2762306a36Sopenharmony_ci * To distinguish an Apple device, a USB host can check the device
2862306a36Sopenharmony_ci * descriptor of attached USB devices for the following fields:
2962306a36Sopenharmony_ci * ■ Vendor ID: 0x05AC
3062306a36Sopenharmony_ci * ■ Product ID: 0x12nn
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * Those checks will be done in .match() and .probe().
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic const struct usb_device_id mfi_fc_id_table[] = {
3662306a36Sopenharmony_ci	{ .idVendor = APPLE_VENDOR_ID,
3762306a36Sopenharmony_ci	  .match_flags = USB_DEVICE_ID_MATCH_VENDOR },
3862306a36Sopenharmony_ci	{},
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, mfi_fc_id_table);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/* Driver-local specific stuff */
4462306a36Sopenharmony_cistruct mfi_device {
4562306a36Sopenharmony_ci	struct usb_device *udev;
4662306a36Sopenharmony_ci	struct power_supply *battery;
4762306a36Sopenharmony_ci	int charge_type;
4862306a36Sopenharmony_ci};
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic int apple_mfi_fc_set_charge_type(struct mfi_device *mfi,
5162306a36Sopenharmony_ci					const union power_supply_propval *val)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	int current_ma;
5462306a36Sopenharmony_ci	int retval;
5562306a36Sopenharmony_ci	__u8 request_type;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (mfi->charge_type == val->intval) {
5862306a36Sopenharmony_ci		dev_dbg(&mfi->udev->dev, "charge type %d already set\n",
5962306a36Sopenharmony_ci				mfi->charge_type);
6062306a36Sopenharmony_ci		return 0;
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	switch (val->intval) {
6462306a36Sopenharmony_ci	case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
6562306a36Sopenharmony_ci		current_ma = TRICKLE_CURRENT_MA;
6662306a36Sopenharmony_ci		break;
6762306a36Sopenharmony_ci	case POWER_SUPPLY_CHARGE_TYPE_FAST:
6862306a36Sopenharmony_ci		current_ma = FAST_CURRENT_MA;
6962306a36Sopenharmony_ci		break;
7062306a36Sopenharmony_ci	default:
7162306a36Sopenharmony_ci		return -EINVAL;
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
7562306a36Sopenharmony_ci	retval = usb_control_msg(mfi->udev, usb_sndctrlpipe(mfi->udev, 0),
7662306a36Sopenharmony_ci				 0x40, /* Vendor‐defined power request */
7762306a36Sopenharmony_ci				 request_type,
7862306a36Sopenharmony_ci				 current_ma, /* wValue, current offset */
7962306a36Sopenharmony_ci				 current_ma, /* wIndex, current offset */
8062306a36Sopenharmony_ci				 NULL, 0, USB_CTRL_GET_TIMEOUT);
8162306a36Sopenharmony_ci	if (retval) {
8262306a36Sopenharmony_ci		dev_dbg(&mfi->udev->dev, "retval = %d\n", retval);
8362306a36Sopenharmony_ci		return retval;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	mfi->charge_type = val->intval;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return 0;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic int apple_mfi_fc_get_property(struct power_supply *psy,
9262306a36Sopenharmony_ci		enum power_supply_property psp,
9362306a36Sopenharmony_ci		union power_supply_propval *val)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	struct mfi_device *mfi = power_supply_get_drvdata(psy);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	dev_dbg(&mfi->udev->dev, "prop: %d\n", psp);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	switch (psp) {
10062306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CHARGE_TYPE:
10162306a36Sopenharmony_ci		val->intval = mfi->charge_type;
10262306a36Sopenharmony_ci		break;
10362306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_SCOPE:
10462306a36Sopenharmony_ci		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
10562306a36Sopenharmony_ci		break;
10662306a36Sopenharmony_ci	default:
10762306a36Sopenharmony_ci		return -ENODATA;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return 0;
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic int apple_mfi_fc_set_property(struct power_supply *psy,
11462306a36Sopenharmony_ci		enum power_supply_property psp,
11562306a36Sopenharmony_ci		const union power_supply_propval *val)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct mfi_device *mfi = power_supply_get_drvdata(psy);
11862306a36Sopenharmony_ci	int ret;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	dev_dbg(&mfi->udev->dev, "prop: %d\n", psp);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	ret = pm_runtime_get_sync(&mfi->udev->dev);
12362306a36Sopenharmony_ci	if (ret < 0) {
12462306a36Sopenharmony_ci		pm_runtime_put_noidle(&mfi->udev->dev);
12562306a36Sopenharmony_ci		return ret;
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	switch (psp) {
12962306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CHARGE_TYPE:
13062306a36Sopenharmony_ci		ret = apple_mfi_fc_set_charge_type(mfi, val);
13162306a36Sopenharmony_ci		break;
13262306a36Sopenharmony_ci	default:
13362306a36Sopenharmony_ci		ret = -EINVAL;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&mfi->udev->dev);
13762306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&mfi->udev->dev);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	return ret;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int apple_mfi_fc_property_is_writeable(struct power_supply *psy,
14362306a36Sopenharmony_ci					      enum power_supply_property psp)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	switch (psp) {
14662306a36Sopenharmony_ci	case POWER_SUPPLY_PROP_CHARGE_TYPE:
14762306a36Sopenharmony_ci		return 1;
14862306a36Sopenharmony_ci	default:
14962306a36Sopenharmony_ci		return 0;
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic enum power_supply_property apple_mfi_fc_properties[] = {
15462306a36Sopenharmony_ci	POWER_SUPPLY_PROP_CHARGE_TYPE,
15562306a36Sopenharmony_ci	POWER_SUPPLY_PROP_SCOPE
15662306a36Sopenharmony_ci};
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic const struct power_supply_desc apple_mfi_fc_desc = {
15962306a36Sopenharmony_ci	.name                   = "apple_mfi_fastcharge",
16062306a36Sopenharmony_ci	.type                   = POWER_SUPPLY_TYPE_BATTERY,
16162306a36Sopenharmony_ci	.properties             = apple_mfi_fc_properties,
16262306a36Sopenharmony_ci	.num_properties         = ARRAY_SIZE(apple_mfi_fc_properties),
16362306a36Sopenharmony_ci	.get_property           = apple_mfi_fc_get_property,
16462306a36Sopenharmony_ci	.set_property           = apple_mfi_fc_set_property,
16562306a36Sopenharmony_ci	.property_is_writeable  = apple_mfi_fc_property_is_writeable
16662306a36Sopenharmony_ci};
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic bool mfi_fc_match(struct usb_device *udev)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	int idProduct;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	idProduct = le16_to_cpu(udev->descriptor.idProduct);
17362306a36Sopenharmony_ci	/* See comment above mfi_fc_id_table[] */
17462306a36Sopenharmony_ci	return (idProduct >= 0x1200 && idProduct <= 0x12ff);
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic int mfi_fc_probe(struct usb_device *udev)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	struct power_supply_config battery_cfg = {};
18062306a36Sopenharmony_ci	struct mfi_device *mfi = NULL;
18162306a36Sopenharmony_ci	int err;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	if (!mfi_fc_match(udev))
18462306a36Sopenharmony_ci		return -ENODEV;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	mfi = kzalloc(sizeof(struct mfi_device), GFP_KERNEL);
18762306a36Sopenharmony_ci	if (!mfi)
18862306a36Sopenharmony_ci		return -ENOMEM;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	battery_cfg.drv_data = mfi;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
19362306a36Sopenharmony_ci	mfi->battery = power_supply_register(&udev->dev,
19462306a36Sopenharmony_ci						&apple_mfi_fc_desc,
19562306a36Sopenharmony_ci						&battery_cfg);
19662306a36Sopenharmony_ci	if (IS_ERR(mfi->battery)) {
19762306a36Sopenharmony_ci		dev_err(&udev->dev, "Can't register battery\n");
19862306a36Sopenharmony_ci		err = PTR_ERR(mfi->battery);
19962306a36Sopenharmony_ci		kfree(mfi);
20062306a36Sopenharmony_ci		return err;
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	mfi->udev = usb_get_dev(udev);
20462306a36Sopenharmony_ci	dev_set_drvdata(&udev->dev, mfi);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	return 0;
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic void mfi_fc_disconnect(struct usb_device *udev)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	struct mfi_device *mfi;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	mfi = dev_get_drvdata(&udev->dev);
21462306a36Sopenharmony_ci	if (mfi->battery)
21562306a36Sopenharmony_ci		power_supply_unregister(mfi->battery);
21662306a36Sopenharmony_ci	dev_set_drvdata(&udev->dev, NULL);
21762306a36Sopenharmony_ci	usb_put_dev(mfi->udev);
21862306a36Sopenharmony_ci	kfree(mfi);
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic struct usb_device_driver mfi_fc_driver = {
22262306a36Sopenharmony_ci	.name =		"apple-mfi-fastcharge",
22362306a36Sopenharmony_ci	.probe =	mfi_fc_probe,
22462306a36Sopenharmony_ci	.disconnect =	mfi_fc_disconnect,
22562306a36Sopenharmony_ci	.id_table =	mfi_fc_id_table,
22662306a36Sopenharmony_ci	.match =	mfi_fc_match,
22762306a36Sopenharmony_ci	.generic_subclass = 1,
22862306a36Sopenharmony_ci};
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic int __init mfi_fc_driver_init(void)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE);
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic void __exit mfi_fc_driver_exit(void)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	usb_deregister_device_driver(&mfi_fc_driver);
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cimodule_init(mfi_fc_driver_init);
24162306a36Sopenharmony_cimodule_exit(mfi_fc_driver_exit);
242