1*249fa821SBastien Nocera // SPDX-License-Identifier: GPL-2.0+ 2*249fa821SBastien Nocera /* 3*249fa821SBastien Nocera * Fast-charge control for Apple "MFi" devices 4*249fa821SBastien Nocera * 5*249fa821SBastien Nocera * Copyright (C) 2019 Bastien Nocera <hadess@hadess.net> 6*249fa821SBastien Nocera */ 7*249fa821SBastien Nocera 8*249fa821SBastien Nocera /* Standard include files */ 9*249fa821SBastien Nocera #include <linux/module.h> 10*249fa821SBastien Nocera #include <linux/power_supply.h> 11*249fa821SBastien Nocera #include <linux/slab.h> 12*249fa821SBastien Nocera #include <linux/usb.h> 13*249fa821SBastien Nocera 14*249fa821SBastien Nocera MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); 15*249fa821SBastien Nocera MODULE_DESCRIPTION("Fast-charge control for Apple \"MFi\" devices"); 16*249fa821SBastien Nocera MODULE_LICENSE("GPL"); 17*249fa821SBastien Nocera 18*249fa821SBastien Nocera #define TRICKLE_CURRENT_MA 0 19*249fa821SBastien Nocera #define FAST_CURRENT_MA 2500 20*249fa821SBastien Nocera 21*249fa821SBastien Nocera #define APPLE_VENDOR_ID 0x05ac /* Apple */ 22*249fa821SBastien Nocera 23*249fa821SBastien Nocera /* The product ID is defined as starting with 0x12nn, as per the 24*249fa821SBastien Nocera * "Choosing an Apple Device USB Configuration" section in 25*249fa821SBastien Nocera * release R9 (2012) of the "MFi Accessory Hardware Specification" 26*249fa821SBastien Nocera * 27*249fa821SBastien Nocera * To distinguish an Apple device, a USB host can check the device 28*249fa821SBastien Nocera * descriptor of attached USB devices for the following fields: 29*249fa821SBastien Nocera * ■ Vendor ID: 0x05AC 30*249fa821SBastien Nocera * ■ Product ID: 0x12nn 31*249fa821SBastien Nocera * 32*249fa821SBastien Nocera * Those checks will be done in .match() and .probe(). 33*249fa821SBastien Nocera */ 34*249fa821SBastien Nocera 35*249fa821SBastien Nocera static const struct usb_device_id mfi_fc_id_table[] = { 36*249fa821SBastien Nocera { .idVendor = APPLE_VENDOR_ID, 37*249fa821SBastien Nocera .match_flags = USB_DEVICE_ID_MATCH_VENDOR }, 38*249fa821SBastien Nocera {}, 39*249fa821SBastien Nocera }; 40*249fa821SBastien Nocera 41*249fa821SBastien Nocera MODULE_DEVICE_TABLE(usb, mfi_fc_id_table); 42*249fa821SBastien Nocera 43*249fa821SBastien Nocera /* Driver-local specific stuff */ 44*249fa821SBastien Nocera struct mfi_device { 45*249fa821SBastien Nocera struct usb_device *udev; 46*249fa821SBastien Nocera struct power_supply *battery; 47*249fa821SBastien Nocera int charge_type; 48*249fa821SBastien Nocera }; 49*249fa821SBastien Nocera 50*249fa821SBastien Nocera static int apple_mfi_fc_set_charge_type(struct mfi_device *mfi, 51*249fa821SBastien Nocera const union power_supply_propval *val) 52*249fa821SBastien Nocera { 53*249fa821SBastien Nocera int current_ma; 54*249fa821SBastien Nocera int retval; 55*249fa821SBastien Nocera __u8 request_type; 56*249fa821SBastien Nocera 57*249fa821SBastien Nocera if (mfi->charge_type == val->intval) { 58*249fa821SBastien Nocera dev_dbg(&mfi->udev->dev, "charge type %d already set\n", 59*249fa821SBastien Nocera mfi->charge_type); 60*249fa821SBastien Nocera return 0; 61*249fa821SBastien Nocera } 62*249fa821SBastien Nocera 63*249fa821SBastien Nocera switch (val->intval) { 64*249fa821SBastien Nocera case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: 65*249fa821SBastien Nocera current_ma = TRICKLE_CURRENT_MA; 66*249fa821SBastien Nocera break; 67*249fa821SBastien Nocera case POWER_SUPPLY_CHARGE_TYPE_FAST: 68*249fa821SBastien Nocera current_ma = FAST_CURRENT_MA; 69*249fa821SBastien Nocera break; 70*249fa821SBastien Nocera default: 71*249fa821SBastien Nocera return -EINVAL; 72*249fa821SBastien Nocera } 73*249fa821SBastien Nocera 74*249fa821SBastien Nocera request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; 75*249fa821SBastien Nocera retval = usb_control_msg(mfi->udev, usb_sndctrlpipe(mfi->udev, 0), 76*249fa821SBastien Nocera 0x40, /* Vendor‐defined power request */ 77*249fa821SBastien Nocera request_type, 78*249fa821SBastien Nocera current_ma, /* wValue, current offset */ 79*249fa821SBastien Nocera current_ma, /* wIndex, current offset */ 80*249fa821SBastien Nocera NULL, 0, USB_CTRL_GET_TIMEOUT); 81*249fa821SBastien Nocera if (retval) { 82*249fa821SBastien Nocera dev_dbg(&mfi->udev->dev, "retval = %d\n", retval); 83*249fa821SBastien Nocera return retval; 84*249fa821SBastien Nocera } 85*249fa821SBastien Nocera 86*249fa821SBastien Nocera mfi->charge_type = val->intval; 87*249fa821SBastien Nocera 88*249fa821SBastien Nocera return 0; 89*249fa821SBastien Nocera } 90*249fa821SBastien Nocera 91*249fa821SBastien Nocera static int apple_mfi_fc_get_property(struct power_supply *psy, 92*249fa821SBastien Nocera enum power_supply_property psp, 93*249fa821SBastien Nocera union power_supply_propval *val) 94*249fa821SBastien Nocera { 95*249fa821SBastien Nocera struct mfi_device *mfi = power_supply_get_drvdata(psy); 96*249fa821SBastien Nocera 97*249fa821SBastien Nocera dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); 98*249fa821SBastien Nocera 99*249fa821SBastien Nocera switch (psp) { 100*249fa821SBastien Nocera case POWER_SUPPLY_PROP_CHARGE_TYPE: 101*249fa821SBastien Nocera val->intval = mfi->charge_type; 102*249fa821SBastien Nocera break; 103*249fa821SBastien Nocera case POWER_SUPPLY_PROP_SCOPE: 104*249fa821SBastien Nocera val->intval = POWER_SUPPLY_SCOPE_DEVICE; 105*249fa821SBastien Nocera break; 106*249fa821SBastien Nocera default: 107*249fa821SBastien Nocera return -ENODATA; 108*249fa821SBastien Nocera } 109*249fa821SBastien Nocera 110*249fa821SBastien Nocera return 0; 111*249fa821SBastien Nocera } 112*249fa821SBastien Nocera 113*249fa821SBastien Nocera static int apple_mfi_fc_set_property(struct power_supply *psy, 114*249fa821SBastien Nocera enum power_supply_property psp, 115*249fa821SBastien Nocera const union power_supply_propval *val) 116*249fa821SBastien Nocera { 117*249fa821SBastien Nocera struct mfi_device *mfi = power_supply_get_drvdata(psy); 118*249fa821SBastien Nocera int ret; 119*249fa821SBastien Nocera 120*249fa821SBastien Nocera dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); 121*249fa821SBastien Nocera 122*249fa821SBastien Nocera ret = pm_runtime_get_sync(&mfi->udev->dev); 123*249fa821SBastien Nocera if (ret < 0) 124*249fa821SBastien Nocera return ret; 125*249fa821SBastien Nocera 126*249fa821SBastien Nocera switch (psp) { 127*249fa821SBastien Nocera case POWER_SUPPLY_PROP_CHARGE_TYPE: 128*249fa821SBastien Nocera ret = apple_mfi_fc_set_charge_type(mfi, val); 129*249fa821SBastien Nocera break; 130*249fa821SBastien Nocera default: 131*249fa821SBastien Nocera ret = -EINVAL; 132*249fa821SBastien Nocera } 133*249fa821SBastien Nocera 134*249fa821SBastien Nocera pm_runtime_mark_last_busy(&mfi->udev->dev); 135*249fa821SBastien Nocera pm_runtime_put_autosuspend(&mfi->udev->dev); 136*249fa821SBastien Nocera 137*249fa821SBastien Nocera return ret; 138*249fa821SBastien Nocera } 139*249fa821SBastien Nocera 140*249fa821SBastien Nocera static int apple_mfi_fc_property_is_writeable(struct power_supply *psy, 141*249fa821SBastien Nocera enum power_supply_property psp) 142*249fa821SBastien Nocera { 143*249fa821SBastien Nocera switch (psp) { 144*249fa821SBastien Nocera case POWER_SUPPLY_PROP_CHARGE_TYPE: 145*249fa821SBastien Nocera return 1; 146*249fa821SBastien Nocera default: 147*249fa821SBastien Nocera return 0; 148*249fa821SBastien Nocera } 149*249fa821SBastien Nocera } 150*249fa821SBastien Nocera 151*249fa821SBastien Nocera static enum power_supply_property apple_mfi_fc_properties[] = { 152*249fa821SBastien Nocera POWER_SUPPLY_PROP_CHARGE_TYPE, 153*249fa821SBastien Nocera POWER_SUPPLY_PROP_SCOPE 154*249fa821SBastien Nocera }; 155*249fa821SBastien Nocera 156*249fa821SBastien Nocera static const struct power_supply_desc apple_mfi_fc_desc = { 157*249fa821SBastien Nocera .name = "apple_mfi_fastcharge", 158*249fa821SBastien Nocera .type = POWER_SUPPLY_TYPE_BATTERY, 159*249fa821SBastien Nocera .properties = apple_mfi_fc_properties, 160*249fa821SBastien Nocera .num_properties = ARRAY_SIZE(apple_mfi_fc_properties), 161*249fa821SBastien Nocera .get_property = apple_mfi_fc_get_property, 162*249fa821SBastien Nocera .set_property = apple_mfi_fc_set_property, 163*249fa821SBastien Nocera .property_is_writeable = apple_mfi_fc_property_is_writeable 164*249fa821SBastien Nocera }; 165*249fa821SBastien Nocera 166*249fa821SBastien Nocera static int mfi_fc_probe(struct usb_device *udev) 167*249fa821SBastien Nocera { 168*249fa821SBastien Nocera struct power_supply_config battery_cfg = {}; 169*249fa821SBastien Nocera struct mfi_device *mfi = NULL; 170*249fa821SBastien Nocera int err; 171*249fa821SBastien Nocera 172*249fa821SBastien Nocera /* See comment above mfi_fc_id_table[] */ 173*249fa821SBastien Nocera if (udev->descriptor.idProduct < 0x1200 || 174*249fa821SBastien Nocera udev->descriptor.idProduct > 0x12ff) { 175*249fa821SBastien Nocera return -ENODEV; 176*249fa821SBastien Nocera } 177*249fa821SBastien Nocera 178*249fa821SBastien Nocera mfi = kzalloc(sizeof(struct mfi_device), GFP_KERNEL); 179*249fa821SBastien Nocera if (!mfi) { 180*249fa821SBastien Nocera err = -ENOMEM; 181*249fa821SBastien Nocera goto error; 182*249fa821SBastien Nocera } 183*249fa821SBastien Nocera 184*249fa821SBastien Nocera battery_cfg.drv_data = mfi; 185*249fa821SBastien Nocera 186*249fa821SBastien Nocera mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 187*249fa821SBastien Nocera mfi->battery = power_supply_register(&udev->dev, 188*249fa821SBastien Nocera &apple_mfi_fc_desc, 189*249fa821SBastien Nocera &battery_cfg); 190*249fa821SBastien Nocera if (IS_ERR(mfi->battery)) { 191*249fa821SBastien Nocera dev_err(&udev->dev, "Can't register battery\n"); 192*249fa821SBastien Nocera err = PTR_ERR(mfi->battery); 193*249fa821SBastien Nocera goto error; 194*249fa821SBastien Nocera } 195*249fa821SBastien Nocera 196*249fa821SBastien Nocera mfi->udev = usb_get_dev(udev); 197*249fa821SBastien Nocera dev_set_drvdata(&udev->dev, mfi); 198*249fa821SBastien Nocera 199*249fa821SBastien Nocera return 0; 200*249fa821SBastien Nocera 201*249fa821SBastien Nocera error: 202*249fa821SBastien Nocera kfree(mfi); 203*249fa821SBastien Nocera return err; 204*249fa821SBastien Nocera } 205*249fa821SBastien Nocera 206*249fa821SBastien Nocera static void mfi_fc_disconnect(struct usb_device *udev) 207*249fa821SBastien Nocera { 208*249fa821SBastien Nocera struct mfi_device *mfi; 209*249fa821SBastien Nocera 210*249fa821SBastien Nocera mfi = dev_get_drvdata(&udev->dev); 211*249fa821SBastien Nocera if (mfi->battery) 212*249fa821SBastien Nocera power_supply_unregister(mfi->battery); 213*249fa821SBastien Nocera dev_set_drvdata(&udev->dev, NULL); 214*249fa821SBastien Nocera usb_put_dev(mfi->udev); 215*249fa821SBastien Nocera kfree(mfi); 216*249fa821SBastien Nocera } 217*249fa821SBastien Nocera 218*249fa821SBastien Nocera static struct usb_device_driver mfi_fc_driver = { 219*249fa821SBastien Nocera .name = "apple-mfi-fastcharge", 220*249fa821SBastien Nocera .probe = mfi_fc_probe, 221*249fa821SBastien Nocera .disconnect = mfi_fc_disconnect, 222*249fa821SBastien Nocera .id_table = mfi_fc_id_table, 223*249fa821SBastien Nocera .generic_subclass = 1, 224*249fa821SBastien Nocera }; 225*249fa821SBastien Nocera 226*249fa821SBastien Nocera static int __init mfi_fc_driver_init(void) 227*249fa821SBastien Nocera { 228*249fa821SBastien Nocera return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE); 229*249fa821SBastien Nocera } 230*249fa821SBastien Nocera 231*249fa821SBastien Nocera static void __exit mfi_fc_driver_exit(void) 232*249fa821SBastien Nocera { 233*249fa821SBastien Nocera usb_deregister_device_driver(&mfi_fc_driver); 234*249fa821SBastien Nocera } 235*249fa821SBastien Nocera 236*249fa821SBastien Nocera module_init(mfi_fc_driver_init); 237*249fa821SBastien Nocera module_exit(mfi_fc_driver_exit); 238