2019-10-16 11:39:33 +02:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Fast - charge control for Apple " MFi " devices
*
* Copyright ( C ) 2019 Bastien Nocera < hadess @ hadess . net >
*/
/* Standard include files */
# include <linux/module.h>
# include <linux/power_supply.h>
# include <linux/slab.h>
# include <linux/usb.h>
MODULE_AUTHOR ( " Bastien Nocera <hadess@hadess.net> " ) ;
MODULE_DESCRIPTION ( " Fast-charge control for Apple \" MFi \" devices " ) ;
MODULE_LICENSE ( " GPL " ) ;
# define TRICKLE_CURRENT_MA 0
# define FAST_CURRENT_MA 2500
# define APPLE_VENDOR_ID 0x05ac /* Apple */
/* The product ID is defined as starting with 0x12nn, as per the
* " Choosing an Apple Device USB Configuration " section in
* release R9 ( 2012 ) of the " MFi Accessory Hardware Specification "
*
* To distinguish an Apple device , a USB host can check the device
* descriptor of attached USB devices for the following fields :
* ■ Vendor ID : 0x05AC
* ■ Product ID : 0x12 nn
*
* Those checks will be done in . match ( ) and . probe ( ) .
*/
static const struct usb_device_id mfi_fc_id_table [ ] = {
{ . idVendor = APPLE_VENDOR_ID ,
. match_flags = USB_DEVICE_ID_MATCH_VENDOR } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( usb , mfi_fc_id_table ) ;
/* Driver-local specific stuff */
struct mfi_device {
struct usb_device * udev ;
struct power_supply * battery ;
int charge_type ;
} ;
static int apple_mfi_fc_set_charge_type ( struct mfi_device * mfi ,
const union power_supply_propval * val )
{
int current_ma ;
int retval ;
__u8 request_type ;
if ( mfi - > charge_type = = val - > intval ) {
dev_dbg ( & mfi - > udev - > dev , " charge type %d already set \n " ,
mfi - > charge_type ) ;
return 0 ;
}
switch ( val - > intval ) {
case POWER_SUPPLY_CHARGE_TYPE_TRICKLE :
current_ma = TRICKLE_CURRENT_MA ;
break ;
case POWER_SUPPLY_CHARGE_TYPE_FAST :
current_ma = FAST_CURRENT_MA ;
break ;
default :
return - EINVAL ;
}
request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE ;
retval = usb_control_msg ( mfi - > udev , usb_sndctrlpipe ( mfi - > udev , 0 ) ,
0x40 , /* Vendor‐ defined power request */
request_type ,
current_ma , /* wValue, current offset */
current_ma , /* wIndex, current offset */
NULL , 0 , USB_CTRL_GET_TIMEOUT ) ;
if ( retval ) {
dev_dbg ( & mfi - > udev - > dev , " retval = %d \n " , retval ) ;
return retval ;
}
mfi - > charge_type = val - > intval ;
return 0 ;
}
static int apple_mfi_fc_get_property ( struct power_supply * psy ,
enum power_supply_property psp ,
union power_supply_propval * val )
{
struct mfi_device * mfi = power_supply_get_drvdata ( psy ) ;
dev_dbg ( & mfi - > udev - > dev , " prop: %d \n " , psp ) ;
switch ( psp ) {
case POWER_SUPPLY_PROP_CHARGE_TYPE :
val - > intval = mfi - > charge_type ;
break ;
case POWER_SUPPLY_PROP_SCOPE :
val - > intval = POWER_SUPPLY_SCOPE_DEVICE ;
break ;
default :
return - ENODATA ;
}
return 0 ;
}
static int apple_mfi_fc_set_property ( struct power_supply * psy ,
enum power_supply_property psp ,
const union power_supply_propval * val )
{
struct mfi_device * mfi = power_supply_get_drvdata ( psy ) ;
int ret ;
dev_dbg ( & mfi - > udev - > dev , " prop: %d \n " , psp ) ;
ret = pm_runtime_get_sync ( & mfi - > udev - > dev ) ;
2020-11-02 10:26:50 +08:00
if ( ret < 0 ) {
pm_runtime_put_noidle ( & mfi - > udev - > dev ) ;
2019-10-16 11:39:33 +02:00
return ret ;
2020-11-02 10:26:50 +08:00
}
2019-10-16 11:39:33 +02:00
switch ( psp ) {
case POWER_SUPPLY_PROP_CHARGE_TYPE :
ret = apple_mfi_fc_set_charge_type ( mfi , val ) ;
break ;
default :
ret = - EINVAL ;
}
pm_runtime_mark_last_busy ( & mfi - > udev - > dev ) ;
pm_runtime_put_autosuspend ( & mfi - > udev - > dev ) ;
return ret ;
}
static int apple_mfi_fc_property_is_writeable ( struct power_supply * psy ,
enum power_supply_property psp )
{
switch ( psp ) {
case POWER_SUPPLY_PROP_CHARGE_TYPE :
return 1 ;
default :
return 0 ;
}
}
static enum power_supply_property apple_mfi_fc_properties [ ] = {
POWER_SUPPLY_PROP_CHARGE_TYPE ,
POWER_SUPPLY_PROP_SCOPE
} ;
static const struct power_supply_desc apple_mfi_fc_desc = {
. name = " apple_mfi_fastcharge " ,
. type = POWER_SUPPLY_TYPE_BATTERY ,
. properties = apple_mfi_fc_properties ,
. num_properties = ARRAY_SIZE ( apple_mfi_fc_properties ) ,
. get_property = apple_mfi_fc_get_property ,
. set_property = apple_mfi_fc_set_property ,
. property_is_writeable = apple_mfi_fc_property_is_writeable
} ;
2020-10-22 09:55:21 -04:00
static bool mfi_fc_match ( struct usb_device * udev )
{
int idProduct ;
idProduct = le16_to_cpu ( udev - > descriptor . idProduct ) ;
/* See comment above mfi_fc_id_table[] */
return ( idProduct > = 0x1200 & & idProduct < = 0x12ff ) ;
}
2019-10-16 11:39:33 +02:00
static int mfi_fc_probe ( struct usb_device * udev )
{
struct power_supply_config battery_cfg = { } ;
struct mfi_device * mfi = NULL ;
2020-12-03 11:42:10 +03:00
int err ;
2019-10-16 11:39:33 +02:00
2020-10-22 09:55:21 -04:00
if ( ! mfi_fc_match ( udev ) )
2019-10-16 11:39:33 +02:00
return - ENODEV ;
mfi = kzalloc ( sizeof ( struct mfi_device ) , GFP_KERNEL ) ;
2020-11-15 10:28:37 +00:00
if ( ! mfi )
return - ENOMEM ;
2019-10-16 11:39:33 +02:00
battery_cfg . drv_data = mfi ;
mfi - > charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE ;
mfi - > battery = power_supply_register ( & udev - > dev ,
& apple_mfi_fc_desc ,
& battery_cfg ) ;
if ( IS_ERR ( mfi - > battery ) ) {
dev_err ( & udev - > dev , " Can't register battery \n " ) ;
2020-12-03 11:42:10 +03:00
err = PTR_ERR ( mfi - > battery ) ;
2020-11-15 10:28:37 +00:00
kfree ( mfi ) ;
2020-12-03 11:42:10 +03:00
return err ;
2019-10-16 11:39:33 +02:00
}
mfi - > udev = usb_get_dev ( udev ) ;
dev_set_drvdata ( & udev - > dev , mfi ) ;
return 0 ;
}
static void mfi_fc_disconnect ( struct usb_device * udev )
{
struct mfi_device * mfi ;
mfi = dev_get_drvdata ( & udev - > dev ) ;
if ( mfi - > battery )
power_supply_unregister ( mfi - > battery ) ;
dev_set_drvdata ( & udev - > dev , NULL ) ;
usb_put_dev ( mfi - > udev ) ;
kfree ( mfi ) ;
}
static struct usb_device_driver mfi_fc_driver = {
. name = " apple-mfi-fastcharge " ,
. probe = mfi_fc_probe ,
. disconnect = mfi_fc_disconnect ,
. id_table = mfi_fc_id_table ,
2020-10-22 09:55:21 -04:00
. match = mfi_fc_match ,
2019-10-16 11:39:33 +02:00
. generic_subclass = 1 ,
} ;
static int __init mfi_fc_driver_init ( void )
{
return usb_register_device_driver ( & mfi_fc_driver , THIS_MODULE ) ;
}
static void __exit mfi_fc_driver_exit ( void )
{
usb_deregister_device_driver ( & mfi_fc_driver ) ;
}
module_init ( mfi_fc_driver_init ) ;
module_exit ( mfi_fc_driver_exit ) ;