18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Fast-charge control for Apple "MFi" devices 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2019 Bastien Nocera <hadess@hadess.net> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci/* Standard include files */ 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/power_supply.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/usb.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ciMODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); 158c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Fast-charge control for Apple \"MFi\" devices"); 168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define TRICKLE_CURRENT_MA 0 198c2ecf20Sopenharmony_ci#define FAST_CURRENT_MA 2500 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define APPLE_VENDOR_ID 0x05ac /* Apple */ 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* The product ID is defined as starting with 0x12nn, as per the 248c2ecf20Sopenharmony_ci * "Choosing an Apple Device USB Configuration" section in 258c2ecf20Sopenharmony_ci * release R9 (2012) of the "MFi Accessory Hardware Specification" 268c2ecf20Sopenharmony_ci * 278c2ecf20Sopenharmony_ci * To distinguish an Apple device, a USB host can check the device 288c2ecf20Sopenharmony_ci * descriptor of attached USB devices for the following fields: 298c2ecf20Sopenharmony_ci * ■ Vendor ID: 0x05AC 308c2ecf20Sopenharmony_ci * ■ Product ID: 0x12nn 318c2ecf20Sopenharmony_ci * 328c2ecf20Sopenharmony_ci * Those checks will be done in .match() and .probe(). 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic const struct usb_device_id mfi_fc_id_table[] = { 368c2ecf20Sopenharmony_ci { .idVendor = APPLE_VENDOR_ID, 378c2ecf20Sopenharmony_ci .match_flags = USB_DEVICE_ID_MATCH_VENDOR }, 388c2ecf20Sopenharmony_ci {}, 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, mfi_fc_id_table); 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci/* Driver-local specific stuff */ 448c2ecf20Sopenharmony_cistruct mfi_device { 458c2ecf20Sopenharmony_ci struct usb_device *udev; 468c2ecf20Sopenharmony_ci struct power_supply *battery; 478c2ecf20Sopenharmony_ci int charge_type; 488c2ecf20Sopenharmony_ci}; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic int apple_mfi_fc_set_charge_type(struct mfi_device *mfi, 518c2ecf20Sopenharmony_ci const union power_supply_propval *val) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci int current_ma; 548c2ecf20Sopenharmony_ci int retval; 558c2ecf20Sopenharmony_ci __u8 request_type; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci if (mfi->charge_type == val->intval) { 588c2ecf20Sopenharmony_ci dev_dbg(&mfi->udev->dev, "charge type %d already set\n", 598c2ecf20Sopenharmony_ci mfi->charge_type); 608c2ecf20Sopenharmony_ci return 0; 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci switch (val->intval) { 648c2ecf20Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: 658c2ecf20Sopenharmony_ci current_ma = TRICKLE_CURRENT_MA; 668c2ecf20Sopenharmony_ci break; 678c2ecf20Sopenharmony_ci case POWER_SUPPLY_CHARGE_TYPE_FAST: 688c2ecf20Sopenharmony_ci current_ma = FAST_CURRENT_MA; 698c2ecf20Sopenharmony_ci break; 708c2ecf20Sopenharmony_ci default: 718c2ecf20Sopenharmony_ci return -EINVAL; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; 758c2ecf20Sopenharmony_ci retval = usb_control_msg(mfi->udev, usb_sndctrlpipe(mfi->udev, 0), 768c2ecf20Sopenharmony_ci 0x40, /* Vendor‐defined power request */ 778c2ecf20Sopenharmony_ci request_type, 788c2ecf20Sopenharmony_ci current_ma, /* wValue, current offset */ 798c2ecf20Sopenharmony_ci current_ma, /* wIndex, current offset */ 808c2ecf20Sopenharmony_ci NULL, 0, USB_CTRL_GET_TIMEOUT); 818c2ecf20Sopenharmony_ci if (retval) { 828c2ecf20Sopenharmony_ci dev_dbg(&mfi->udev->dev, "retval = %d\n", retval); 838c2ecf20Sopenharmony_ci return retval; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci mfi->charge_type = val->intval; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return 0; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic int apple_mfi_fc_get_property(struct power_supply *psy, 928c2ecf20Sopenharmony_ci enum power_supply_property psp, 938c2ecf20Sopenharmony_ci union power_supply_propval *val) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct mfi_device *mfi = power_supply_get_drvdata(psy); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci switch (psp) { 1008c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_TYPE: 1018c2ecf20Sopenharmony_ci val->intval = mfi->charge_type; 1028c2ecf20Sopenharmony_ci break; 1038c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_SCOPE: 1048c2ecf20Sopenharmony_ci val->intval = POWER_SUPPLY_SCOPE_DEVICE; 1058c2ecf20Sopenharmony_ci break; 1068c2ecf20Sopenharmony_ci default: 1078c2ecf20Sopenharmony_ci return -ENODATA; 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci return 0; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic int apple_mfi_fc_set_property(struct power_supply *psy, 1148c2ecf20Sopenharmony_ci enum power_supply_property psp, 1158c2ecf20Sopenharmony_ci const union power_supply_propval *val) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci struct mfi_device *mfi = power_supply_get_drvdata(psy); 1188c2ecf20Sopenharmony_ci int ret; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci ret = pm_runtime_get_sync(&mfi->udev->dev); 1238c2ecf20Sopenharmony_ci if (ret < 0) { 1248c2ecf20Sopenharmony_ci pm_runtime_put_noidle(&mfi->udev->dev); 1258c2ecf20Sopenharmony_ci return ret; 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci switch (psp) { 1298c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_TYPE: 1308c2ecf20Sopenharmony_ci ret = apple_mfi_fc_set_charge_type(mfi, val); 1318c2ecf20Sopenharmony_ci break; 1328c2ecf20Sopenharmony_ci default: 1338c2ecf20Sopenharmony_ci ret = -EINVAL; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci pm_runtime_mark_last_busy(&mfi->udev->dev); 1378c2ecf20Sopenharmony_ci pm_runtime_put_autosuspend(&mfi->udev->dev); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci return ret; 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic int apple_mfi_fc_property_is_writeable(struct power_supply *psy, 1438c2ecf20Sopenharmony_ci enum power_supply_property psp) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci switch (psp) { 1468c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_TYPE: 1478c2ecf20Sopenharmony_ci return 1; 1488c2ecf20Sopenharmony_ci default: 1498c2ecf20Sopenharmony_ci return 0; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic enum power_supply_property apple_mfi_fc_properties[] = { 1548c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_TYPE, 1558c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_SCOPE 1568c2ecf20Sopenharmony_ci}; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic const struct power_supply_desc apple_mfi_fc_desc = { 1598c2ecf20Sopenharmony_ci .name = "apple_mfi_fastcharge", 1608c2ecf20Sopenharmony_ci .type = POWER_SUPPLY_TYPE_BATTERY, 1618c2ecf20Sopenharmony_ci .properties = apple_mfi_fc_properties, 1628c2ecf20Sopenharmony_ci .num_properties = ARRAY_SIZE(apple_mfi_fc_properties), 1638c2ecf20Sopenharmony_ci .get_property = apple_mfi_fc_get_property, 1648c2ecf20Sopenharmony_ci .set_property = apple_mfi_fc_set_property, 1658c2ecf20Sopenharmony_ci .property_is_writeable = apple_mfi_fc_property_is_writeable 1668c2ecf20Sopenharmony_ci}; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic bool mfi_fc_match(struct usb_device *udev) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci int idProduct; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci idProduct = le16_to_cpu(udev->descriptor.idProduct); 1738c2ecf20Sopenharmony_ci /* See comment above mfi_fc_id_table[] */ 1748c2ecf20Sopenharmony_ci return (idProduct >= 0x1200 && idProduct <= 0x12ff); 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic int mfi_fc_probe(struct usb_device *udev) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci struct power_supply_config battery_cfg = {}; 1808c2ecf20Sopenharmony_ci struct mfi_device *mfi = NULL; 1818c2ecf20Sopenharmony_ci int err; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci if (!mfi_fc_match(udev)) 1848c2ecf20Sopenharmony_ci return -ENODEV; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci mfi = kzalloc(sizeof(struct mfi_device), GFP_KERNEL); 1878c2ecf20Sopenharmony_ci if (!mfi) { 1888c2ecf20Sopenharmony_ci err = -ENOMEM; 1898c2ecf20Sopenharmony_ci goto error; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci battery_cfg.drv_data = mfi; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 1958c2ecf20Sopenharmony_ci mfi->battery = power_supply_register(&udev->dev, 1968c2ecf20Sopenharmony_ci &apple_mfi_fc_desc, 1978c2ecf20Sopenharmony_ci &battery_cfg); 1988c2ecf20Sopenharmony_ci if (IS_ERR(mfi->battery)) { 1998c2ecf20Sopenharmony_ci dev_err(&udev->dev, "Can't register battery\n"); 2008c2ecf20Sopenharmony_ci err = PTR_ERR(mfi->battery); 2018c2ecf20Sopenharmony_ci goto error; 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci mfi->udev = usb_get_dev(udev); 2058c2ecf20Sopenharmony_ci dev_set_drvdata(&udev->dev, mfi); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci return 0; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cierror: 2108c2ecf20Sopenharmony_ci kfree(mfi); 2118c2ecf20Sopenharmony_ci return err; 2128c2ecf20Sopenharmony_ci} 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cistatic void mfi_fc_disconnect(struct usb_device *udev) 2158c2ecf20Sopenharmony_ci{ 2168c2ecf20Sopenharmony_ci struct mfi_device *mfi; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci mfi = dev_get_drvdata(&udev->dev); 2198c2ecf20Sopenharmony_ci if (mfi->battery) 2208c2ecf20Sopenharmony_ci power_supply_unregister(mfi->battery); 2218c2ecf20Sopenharmony_ci dev_set_drvdata(&udev->dev, NULL); 2228c2ecf20Sopenharmony_ci usb_put_dev(mfi->udev); 2238c2ecf20Sopenharmony_ci kfree(mfi); 2248c2ecf20Sopenharmony_ci} 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_cistatic struct usb_device_driver mfi_fc_driver = { 2278c2ecf20Sopenharmony_ci .name = "apple-mfi-fastcharge", 2288c2ecf20Sopenharmony_ci .probe = mfi_fc_probe, 2298c2ecf20Sopenharmony_ci .disconnect = mfi_fc_disconnect, 2308c2ecf20Sopenharmony_ci .id_table = mfi_fc_id_table, 2318c2ecf20Sopenharmony_ci .match = mfi_fc_match, 2328c2ecf20Sopenharmony_ci .generic_subclass = 1, 2338c2ecf20Sopenharmony_ci}; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic int __init mfi_fc_driver_init(void) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE); 2388c2ecf20Sopenharmony_ci} 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_cistatic void __exit mfi_fc_driver_exit(void) 2418c2ecf20Sopenharmony_ci{ 2428c2ecf20Sopenharmony_ci usb_deregister_device_driver(&mfi_fc_driver); 2438c2ecf20Sopenharmony_ci} 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_cimodule_init(mfi_fc_driver_init); 2468c2ecf20Sopenharmony_cimodule_exit(mfi_fc_driver_exit); 247