1249fa821SBastien Nocera // SPDX-License-Identifier: GPL-2.0+ 2249fa821SBastien Nocera /* 3249fa821SBastien Nocera * Fast-charge control for Apple "MFi" devices 4249fa821SBastien Nocera * 5249fa821SBastien Nocera * Copyright (C) 2019 Bastien Nocera <hadess@hadess.net> 6249fa821SBastien Nocera */ 7249fa821SBastien Nocera 8249fa821SBastien Nocera /* Standard include files */ 9249fa821SBastien Nocera #include <linux/module.h> 10249fa821SBastien Nocera #include <linux/power_supply.h> 11249fa821SBastien Nocera #include <linux/slab.h> 12249fa821SBastien Nocera #include <linux/usb.h> 13249fa821SBastien Nocera 14249fa821SBastien Nocera MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); 15249fa821SBastien Nocera MODULE_DESCRIPTION("Fast-charge control for Apple \"MFi\" devices"); 16249fa821SBastien Nocera MODULE_LICENSE("GPL"); 17249fa821SBastien Nocera 18249fa821SBastien Nocera #define TRICKLE_CURRENT_MA 0 19249fa821SBastien Nocera #define FAST_CURRENT_MA 2500 20249fa821SBastien Nocera 21249fa821SBastien Nocera #define APPLE_VENDOR_ID 0x05ac /* Apple */ 22249fa821SBastien Nocera 23249fa821SBastien Nocera /* The product ID is defined as starting with 0x12nn, as per the 24249fa821SBastien Nocera * "Choosing an Apple Device USB Configuration" section in 25249fa821SBastien Nocera * release R9 (2012) of the "MFi Accessory Hardware Specification" 26249fa821SBastien Nocera * 27249fa821SBastien Nocera * To distinguish an Apple device, a USB host can check the device 28249fa821SBastien Nocera * descriptor of attached USB devices for the following fields: 29249fa821SBastien Nocera * ■ Vendor ID: 0x05AC 30249fa821SBastien Nocera * ■ Product ID: 0x12nn 31249fa821SBastien Nocera * 32249fa821SBastien Nocera * Those checks will be done in .match() and .probe(). 33249fa821SBastien Nocera */ 34249fa821SBastien Nocera 35249fa821SBastien Nocera static const struct usb_device_id mfi_fc_id_table[] = { 36249fa821SBastien Nocera { .idVendor = APPLE_VENDOR_ID, 37249fa821SBastien Nocera .match_flags = USB_DEVICE_ID_MATCH_VENDOR }, 38249fa821SBastien Nocera {}, 39249fa821SBastien Nocera }; 40249fa821SBastien Nocera 41249fa821SBastien Nocera MODULE_DEVICE_TABLE(usb, mfi_fc_id_table); 42249fa821SBastien Nocera 43249fa821SBastien Nocera /* Driver-local specific stuff */ 44249fa821SBastien Nocera struct mfi_device { 45249fa821SBastien Nocera struct usb_device *udev; 46249fa821SBastien Nocera struct power_supply *battery; 47249fa821SBastien Nocera int charge_type; 48249fa821SBastien Nocera }; 49249fa821SBastien Nocera 50249fa821SBastien Nocera static int apple_mfi_fc_set_charge_type(struct mfi_device *mfi, 51249fa821SBastien Nocera const union power_supply_propval *val) 52249fa821SBastien Nocera { 53249fa821SBastien Nocera int current_ma; 54249fa821SBastien Nocera int retval; 55249fa821SBastien Nocera __u8 request_type; 56249fa821SBastien Nocera 57249fa821SBastien Nocera if (mfi->charge_type == val->intval) { 58249fa821SBastien Nocera dev_dbg(&mfi->udev->dev, "charge type %d already set\n", 59249fa821SBastien Nocera mfi->charge_type); 60249fa821SBastien Nocera return 0; 61249fa821SBastien Nocera } 62249fa821SBastien Nocera 63249fa821SBastien Nocera switch (val->intval) { 64249fa821SBastien Nocera case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: 65249fa821SBastien Nocera current_ma = TRICKLE_CURRENT_MA; 66249fa821SBastien Nocera break; 67249fa821SBastien Nocera case POWER_SUPPLY_CHARGE_TYPE_FAST: 68249fa821SBastien Nocera current_ma = FAST_CURRENT_MA; 69249fa821SBastien Nocera break; 70249fa821SBastien Nocera default: 71249fa821SBastien Nocera return -EINVAL; 72249fa821SBastien Nocera } 73249fa821SBastien Nocera 74249fa821SBastien Nocera request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; 75249fa821SBastien Nocera retval = usb_control_msg(mfi->udev, usb_sndctrlpipe(mfi->udev, 0), 76249fa821SBastien Nocera 0x40, /* Vendor‐defined power request */ 77249fa821SBastien Nocera request_type, 78249fa821SBastien Nocera current_ma, /* wValue, current offset */ 79249fa821SBastien Nocera current_ma, /* wIndex, current offset */ 80249fa821SBastien Nocera NULL, 0, USB_CTRL_GET_TIMEOUT); 81249fa821SBastien Nocera if (retval) { 82249fa821SBastien Nocera dev_dbg(&mfi->udev->dev, "retval = %d\n", retval); 83249fa821SBastien Nocera return retval; 84249fa821SBastien Nocera } 85249fa821SBastien Nocera 86249fa821SBastien Nocera mfi->charge_type = val->intval; 87249fa821SBastien Nocera 88249fa821SBastien Nocera return 0; 89249fa821SBastien Nocera } 90249fa821SBastien Nocera 91249fa821SBastien Nocera static int apple_mfi_fc_get_property(struct power_supply *psy, 92249fa821SBastien Nocera enum power_supply_property psp, 93249fa821SBastien Nocera union power_supply_propval *val) 94249fa821SBastien Nocera { 95249fa821SBastien Nocera struct mfi_device *mfi = power_supply_get_drvdata(psy); 96249fa821SBastien Nocera 97249fa821SBastien Nocera dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); 98249fa821SBastien Nocera 99249fa821SBastien Nocera switch (psp) { 100249fa821SBastien Nocera case POWER_SUPPLY_PROP_CHARGE_TYPE: 101249fa821SBastien Nocera val->intval = mfi->charge_type; 102249fa821SBastien Nocera break; 103249fa821SBastien Nocera case POWER_SUPPLY_PROP_SCOPE: 104249fa821SBastien Nocera val->intval = POWER_SUPPLY_SCOPE_DEVICE; 105249fa821SBastien Nocera break; 106249fa821SBastien Nocera default: 107249fa821SBastien Nocera return -ENODATA; 108249fa821SBastien Nocera } 109249fa821SBastien Nocera 110249fa821SBastien Nocera return 0; 111249fa821SBastien Nocera } 112249fa821SBastien Nocera 113249fa821SBastien Nocera static int apple_mfi_fc_set_property(struct power_supply *psy, 114249fa821SBastien Nocera enum power_supply_property psp, 115249fa821SBastien Nocera const union power_supply_propval *val) 116249fa821SBastien Nocera { 117249fa821SBastien Nocera struct mfi_device *mfi = power_supply_get_drvdata(psy); 118249fa821SBastien Nocera int ret; 119249fa821SBastien Nocera 120249fa821SBastien Nocera dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); 121249fa821SBastien Nocera 122249fa821SBastien Nocera ret = pm_runtime_get_sync(&mfi->udev->dev); 12300bd6bcaSZhang Qilong if (ret < 0) { 12400bd6bcaSZhang Qilong pm_runtime_put_noidle(&mfi->udev->dev); 125249fa821SBastien Nocera return ret; 12600bd6bcaSZhang Qilong } 127249fa821SBastien Nocera 128249fa821SBastien Nocera switch (psp) { 129249fa821SBastien Nocera case POWER_SUPPLY_PROP_CHARGE_TYPE: 130249fa821SBastien Nocera ret = apple_mfi_fc_set_charge_type(mfi, val); 131249fa821SBastien Nocera break; 132249fa821SBastien Nocera default: 133249fa821SBastien Nocera ret = -EINVAL; 134249fa821SBastien Nocera } 135249fa821SBastien Nocera 136249fa821SBastien Nocera pm_runtime_mark_last_busy(&mfi->udev->dev); 137249fa821SBastien Nocera pm_runtime_put_autosuspend(&mfi->udev->dev); 138249fa821SBastien Nocera 139249fa821SBastien Nocera return ret; 140249fa821SBastien Nocera } 141249fa821SBastien Nocera 142249fa821SBastien Nocera static int apple_mfi_fc_property_is_writeable(struct power_supply *psy, 143249fa821SBastien Nocera enum power_supply_property psp) 144249fa821SBastien Nocera { 145249fa821SBastien Nocera switch (psp) { 146249fa821SBastien Nocera case POWER_SUPPLY_PROP_CHARGE_TYPE: 147249fa821SBastien Nocera return 1; 148249fa821SBastien Nocera default: 149249fa821SBastien Nocera return 0; 150249fa821SBastien Nocera } 151249fa821SBastien Nocera } 152249fa821SBastien Nocera 153249fa821SBastien Nocera static enum power_supply_property apple_mfi_fc_properties[] = { 154249fa821SBastien Nocera POWER_SUPPLY_PROP_CHARGE_TYPE, 155249fa821SBastien Nocera POWER_SUPPLY_PROP_SCOPE 156249fa821SBastien Nocera }; 157249fa821SBastien Nocera 158249fa821SBastien Nocera static const struct power_supply_desc apple_mfi_fc_desc = { 159249fa821SBastien Nocera .name = "apple_mfi_fastcharge", 160249fa821SBastien Nocera .type = POWER_SUPPLY_TYPE_BATTERY, 161249fa821SBastien Nocera .properties = apple_mfi_fc_properties, 162249fa821SBastien Nocera .num_properties = ARRAY_SIZE(apple_mfi_fc_properties), 163249fa821SBastien Nocera .get_property = apple_mfi_fc_get_property, 164249fa821SBastien Nocera .set_property = apple_mfi_fc_set_property, 165249fa821SBastien Nocera .property_is_writeable = apple_mfi_fc_property_is_writeable 166249fa821SBastien Nocera }; 167249fa821SBastien Nocera 1680cb68669SBastien Nocera static bool mfi_fc_match(struct usb_device *udev) 1690cb68669SBastien Nocera { 1700cb68669SBastien Nocera int idProduct; 1710cb68669SBastien Nocera 1720cb68669SBastien Nocera idProduct = le16_to_cpu(udev->descriptor.idProduct); 1730cb68669SBastien Nocera /* See comment above mfi_fc_id_table[] */ 1740cb68669SBastien Nocera return (idProduct >= 0x1200 && idProduct <= 0x12ff); 1750cb68669SBastien Nocera } 1760cb68669SBastien Nocera 177249fa821SBastien Nocera static int mfi_fc_probe(struct usb_device *udev) 178249fa821SBastien Nocera { 179249fa821SBastien Nocera struct power_supply_config battery_cfg = {}; 180249fa821SBastien Nocera struct mfi_device *mfi = NULL; 181*cf5fbe02SDan Carpenter int err; 182249fa821SBastien Nocera 1830cb68669SBastien Nocera if (!mfi_fc_match(udev)) 184249fa821SBastien Nocera return -ENODEV; 185249fa821SBastien Nocera 186249fa821SBastien Nocera mfi = kzalloc(sizeof(struct mfi_device), GFP_KERNEL); 187b0eec52fSLucas Tanure if (!mfi) 188b0eec52fSLucas Tanure return -ENOMEM; 189249fa821SBastien Nocera 190249fa821SBastien Nocera battery_cfg.drv_data = mfi; 191249fa821SBastien Nocera 192249fa821SBastien Nocera mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 193249fa821SBastien Nocera mfi->battery = power_supply_register(&udev->dev, 194249fa821SBastien Nocera &apple_mfi_fc_desc, 195249fa821SBastien Nocera &battery_cfg); 196249fa821SBastien Nocera if (IS_ERR(mfi->battery)) { 197249fa821SBastien Nocera dev_err(&udev->dev, "Can't register battery\n"); 198*cf5fbe02SDan Carpenter err = PTR_ERR(mfi->battery); 199b0eec52fSLucas Tanure kfree(mfi); 200*cf5fbe02SDan Carpenter return err; 201249fa821SBastien Nocera } 202249fa821SBastien Nocera 203249fa821SBastien Nocera mfi->udev = usb_get_dev(udev); 204249fa821SBastien Nocera dev_set_drvdata(&udev->dev, mfi); 205249fa821SBastien Nocera 206249fa821SBastien Nocera return 0; 207249fa821SBastien Nocera } 208249fa821SBastien Nocera 209249fa821SBastien Nocera static void mfi_fc_disconnect(struct usb_device *udev) 210249fa821SBastien Nocera { 211249fa821SBastien Nocera struct mfi_device *mfi; 212249fa821SBastien Nocera 213249fa821SBastien Nocera mfi = dev_get_drvdata(&udev->dev); 214249fa821SBastien Nocera if (mfi->battery) 215249fa821SBastien Nocera power_supply_unregister(mfi->battery); 216249fa821SBastien Nocera dev_set_drvdata(&udev->dev, NULL); 217249fa821SBastien Nocera usb_put_dev(mfi->udev); 218249fa821SBastien Nocera kfree(mfi); 219249fa821SBastien Nocera } 220249fa821SBastien Nocera 221249fa821SBastien Nocera static struct usb_device_driver mfi_fc_driver = { 222249fa821SBastien Nocera .name = "apple-mfi-fastcharge", 223249fa821SBastien Nocera .probe = mfi_fc_probe, 224249fa821SBastien Nocera .disconnect = mfi_fc_disconnect, 225249fa821SBastien Nocera .id_table = mfi_fc_id_table, 2260cb68669SBastien Nocera .match = mfi_fc_match, 227249fa821SBastien Nocera .generic_subclass = 1, 228249fa821SBastien Nocera }; 229249fa821SBastien Nocera 230249fa821SBastien Nocera static int __init mfi_fc_driver_init(void) 231249fa821SBastien Nocera { 232249fa821SBastien Nocera return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE); 233249fa821SBastien Nocera } 234249fa821SBastien Nocera 235249fa821SBastien Nocera static void __exit mfi_fc_driver_exit(void) 236249fa821SBastien Nocera { 237249fa821SBastien Nocera usb_deregister_device_driver(&mfi_fc_driver); 238249fa821SBastien Nocera } 239249fa821SBastien Nocera 240249fa821SBastien Nocera module_init(mfi_fc_driver_init); 241249fa821SBastien Nocera module_exit(mfi_fc_driver_exit); 242