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