2011-11-24 13:12:18 +04:00
/*
2012-05-09 07:31:58 +04:00
* extcon - max8997 . c - MAX8997 extcon driver to support MAX8997 MUIC
2011-11-24 13:12:18 +04:00
*
2012-11-20 10:46:33 +04:00
* Copyright ( C ) 2012 Samsung Electronics
2011-11-24 13:12:18 +04:00
* Donggeun Kim < dg77 . kim @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/err.h>
# include <linux/platform_device.h>
# include <linux/kobject.h>
# include <linux/mfd/max8997.h>
# include <linux/mfd/max8997-private.h>
2012-05-09 07:31:58 +04:00
# include <linux/extcon.h>
2012-07-02 04:03:00 +04:00
# include <linux/irqdomain.h>
2012-05-09 07:31:58 +04:00
# define DEV_NAME "max8997-muic"
2013-02-13 10:10:00 +04:00
# define DELAY_MS_DEFAULT 20000 /* unit: millisecond */
2011-11-24 13:12:18 +04:00
2013-02-13 03:50:00 +04:00
enum max8997_muic_adc_debounce_time {
ADC_DEBOUNCE_TIME_0_5MS = 0 , /* 0.5ms */
ADC_DEBOUNCE_TIME_10MS , /* 10ms */
ADC_DEBOUNCE_TIME_25MS , /* 25ms */
ADC_DEBOUNCE_TIME_38_62MS , /* 38.62ms */
} ;
2011-11-24 13:12:18 +04:00
struct max8997_muic_irq {
unsigned int irq ;
const char * name ;
2012-07-02 04:03:00 +04:00
unsigned int virq ;
2011-11-24 13:12:18 +04:00
} ;
static struct max8997_muic_irq muic_irqs [ ] = {
2013-02-13 07:05:42 +04:00
{ MAX8997_MUICIRQ_ADCError , " muic-ADCERROR " } ,
{ MAX8997_MUICIRQ_ADCLow , " muic-ADCLOW " } ,
{ MAX8997_MUICIRQ_ADC , " muic-ADC " } ,
{ MAX8997_MUICIRQ_VBVolt , " muic-VBVOLT " } ,
{ MAX8997_MUICIRQ_DBChg , " muic-DBCHG " } ,
{ MAX8997_MUICIRQ_DCDTmr , " muic-DCDTMR " } ,
{ MAX8997_MUICIRQ_ChgDetRun , " muic-CHGDETRUN " } ,
{ MAX8997_MUICIRQ_ChgTyp , " muic-CHGTYP " } ,
{ MAX8997_MUICIRQ_OVP , " muic-OVP " } ,
} ;
/* Define supported cable type */
enum max8997_muic_acc_type {
MAX8997_MUIC_ADC_GROUND = 0x0 ,
MAX8997_MUIC_ADC_MHL , /* MHL*/
MAX8997_MUIC_ADC_REMOTE_S1_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S2_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S3_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S4_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S5_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S6_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S7_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S8_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S9_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S10_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S11_BUTTON ,
MAX8997_MUIC_ADC_REMOTE_S12_BUTTON ,
MAX8997_MUIC_ADC_RESERVED_ACC_1 ,
MAX8997_MUIC_ADC_RESERVED_ACC_2 ,
MAX8997_MUIC_ADC_RESERVED_ACC_3 ,
MAX8997_MUIC_ADC_RESERVED_ACC_4 ,
MAX8997_MUIC_ADC_RESERVED_ACC_5 ,
MAX8997_MUIC_ADC_CEA936_AUDIO ,
MAX8997_MUIC_ADC_PHONE_POWERED_DEV ,
MAX8997_MUIC_ADC_TTY_CONVERTER ,
MAX8997_MUIC_ADC_UART_CABLE ,
MAX8997_MUIC_ADC_CEA936A_TYPE1_CHG ,
MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF , /* JIG-USB-OFF */
MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON , /* JIG-USB-ON */
MAX8997_MUIC_ADC_AV_CABLE_NOLOAD , /* DESKDOCK */
MAX8997_MUIC_ADC_CEA936A_TYPE2_CHG ,
MAX8997_MUIC_ADC_FACTORY_MODE_UART_OFF , /* JIG-UART */
MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON , /* CARDOCK */
MAX8997_MUIC_ADC_AUDIO_MODE_REMOTE ,
MAX8997_MUIC_ADC_OPEN , /* OPEN */
} ;
enum max8997_muic_cable_group {
MAX8997_CABLE_GROUP_ADC = 0 ,
MAX8997_CABLE_GROUP_ADC_GND ,
MAX8997_CABLE_GROUP_CHG ,
MAX8997_CABLE_GROUP_VBVOLT ,
} ;
enum max8997_muic_usb_type {
MAX8997_USB_HOST ,
MAX8997_USB_DEVICE ,
} ;
enum max8997_muic_charger_type {
MAX8997_CHARGER_TYPE_NONE = 0 ,
MAX8997_CHARGER_TYPE_USB ,
MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT ,
MAX8997_CHARGER_TYPE_DEDICATED_CHG ,
MAX8997_CHARGER_TYPE_500MA ,
MAX8997_CHARGER_TYPE_1A ,
MAX8997_CHARGER_TYPE_DEAD_BATTERY = 7 ,
2011-11-24 13:12:18 +04:00
} ;
struct max8997_muic_info {
struct device * dev ;
struct i2c_client * muic ;
2013-02-13 07:05:42 +04:00
struct extcon_dev * edev ;
int prev_cable_type ;
int prev_chg_type ;
u8 status [ 2 ] ;
2011-11-24 13:12:18 +04:00
int irq ;
struct work_struct irq_work ;
struct mutex mutex ;
2012-05-09 07:31:58 +04:00
2013-02-13 07:05:42 +04:00
struct max8997_muic_platform_data * muic_pdata ;
enum max8997_muic_charger_type pre_charger_type ;
2013-02-13 10:04:15 +04:00
2013-02-13 10:10:00 +04:00
/*
* Use delayed workqueue to detect cable state and then
* notify cable state to notifiee / platform through uevent .
* After completing the booting of platform , the extcon provider
* driver should notify cable state to upper layer .
*/
struct delayed_work wq_detcable ;
2013-02-13 10:04:15 +04:00
/*
* Default usb / uart path whether UART / USB or AUX_UART / AUX_USB
* h / w path of COMP2 / COMN1 on CONTROL1 register .
*/
int path_usb ;
int path_uart ;
2012-05-09 07:31:58 +04:00
} ;
2013-02-12 15:44:19 +04:00
enum {
EXTCON_CABLE_USB = 0 ,
EXTCON_CABLE_USB_HOST ,
EXTCON_CABLE_TA ,
EXTCON_CABLE_FAST_CHARGER ,
EXTCON_CABLE_SLOW_CHARGER ,
EXTCON_CABLE_CHARGE_DOWNSTREAM ,
EXTCON_CABLE_MHL ,
EXTCON_CABLE_DOCK_DESK ,
EXTCON_CABLE_DOCK_CARD ,
EXTCON_CABLE_JIG ,
_EXTCON_CABLE_NUM ,
} ;
2013-01-31 04:29:29 +04:00
static const char * max8997_extcon_cable [ ] = {
2013-02-12 15:44:19 +04:00
[ EXTCON_CABLE_USB ] = " USB " ,
[ EXTCON_CABLE_USB_HOST ] = " USB-Host " ,
[ EXTCON_CABLE_TA ] = " TA " ,
[ EXTCON_CABLE_FAST_CHARGER ] = " Fast-charger " ,
[ EXTCON_CABLE_SLOW_CHARGER ] = " Slow-charger " ,
[ EXTCON_CABLE_CHARGE_DOWNSTREAM ] = " Charge-downstream " ,
[ EXTCON_CABLE_MHL ] = " MHL " ,
[ EXTCON_CABLE_DOCK_DESK ] = " Dock-Desk " ,
[ EXTCON_CABLE_DOCK_CARD ] = " Dock-Card " ,
[ EXTCON_CABLE_JIG ] = " JIG " ,
2012-05-09 07:31:58 +04:00
NULL ,
2011-11-24 13:12:18 +04:00
} ;
2013-02-13 03:50:00 +04:00
/*
* max8997_muic_set_debounce_time - Set the debounce time of ADC
* @ info : the instance including private data of max8997 MUIC
* @ time : the debounce time of ADC
*/
static int max8997_muic_set_debounce_time ( struct max8997_muic_info * info ,
enum max8997_muic_adc_debounce_time time )
{
int ret ;
switch ( time ) {
case ADC_DEBOUNCE_TIME_0_5MS :
case ADC_DEBOUNCE_TIME_10MS :
case ADC_DEBOUNCE_TIME_25MS :
case ADC_DEBOUNCE_TIME_38_62MS :
ret = max8997_update_reg ( info - > muic ,
MAX8997_MUIC_REG_CONTROL3 ,
time < < CONTROL3_ADCDBSET_SHIFT ,
CONTROL3_ADCDBSET_MASK ) ;
if ( ret ) {
dev_err ( info - > dev , " failed to set ADC debounce time \n " ) ;
2013-04-08 04:13:18 +04:00
return ret ;
2013-02-13 03:50:00 +04:00
}
break ;
default :
dev_err ( info - > dev , " invalid ADC debounce time \n " ) ;
return - EINVAL ;
}
return 0 ;
} ;
2013-02-13 03:42:37 +04:00
/*
* max8997_muic_set_path - Set hardware line according to attached cable
* @ info : the instance including private data of max8997 MUIC
* @ value : the path according to attached cable
* @ attached : the state of cable ( true : attached , false : detached )
*
* The max8997 MUIC device share outside H / W line among a varity of cables ,
* so this function set internal path of H / W line according to the type of
* attached cable .
*/
static int max8997_muic_set_path ( struct max8997_muic_info * info ,
u8 val , bool attached )
{
int ret = 0 ;
u8 ctrl1 , ctrl2 = 0 ;
if ( attached )
ctrl1 = val ;
else
ctrl1 = CONTROL1_SW_OPEN ;
ret = max8997_update_reg ( info - > muic ,
MAX8997_MUIC_REG_CONTROL1 , ctrl1 , COMP_SW_MASK ) ;
if ( ret < 0 ) {
dev_err ( info - > dev , " failed to update MUIC register \n " ) ;
2013-04-08 04:13:18 +04:00
return ret ;
2013-02-13 03:42:37 +04:00
}
if ( attached )
ctrl2 | = CONTROL2_CPEN_MASK ; /* LowPwr=0, CPEn=1 */
else
ctrl2 | = CONTROL2_LOWPWR_MASK ; /* LowPwr=1, CPEn=0 */
ret = max8997_update_reg ( info - > muic ,
MAX8997_MUIC_REG_CONTROL2 , ctrl2 ,
CONTROL2_LOWPWR_MASK | CONTROL2_CPEN_MASK ) ;
if ( ret < 0 ) {
dev_err ( info - > dev , " failed to update MUIC register \n " ) ;
2013-04-08 04:13:18 +04:00
return ret ;
2013-02-13 03:42:37 +04:00
}
dev_info ( info - > dev ,
" CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s \n " ,
ctrl1 , ctrl2 , attached ? " attached " : " detached " ) ;
return 0 ;
}
2013-02-13 07:05:42 +04:00
/*
* max8997_muic_get_cable_type - Return cable type and check cable state
* @ info : the instance including private data of max8997 MUIC
* @ group : the path according to attached cable
* @ attached : store cable state and return
*
* This function check the cable state either attached or detached ,
* and then divide precise type of cable according to cable group .
* - MAX8997_CABLE_GROUP_ADC
* - MAX8997_CABLE_GROUP_CHG
*/
static int max8997_muic_get_cable_type ( struct max8997_muic_info * info ,
enum max8997_muic_cable_group group , bool * attached )
{
int cable_type = 0 ;
int adc ;
int chg_type ;
switch ( group ) {
case MAX8997_CABLE_GROUP_ADC :
/*
* Read ADC value to check cable type and decide cable state
* according to cable type
*/
adc = info - > status [ 0 ] & STATUS1_ADC_MASK ;
adc > > = STATUS1_ADC_SHIFT ;
/*
* Check current cable state / cable type and store cable type
* ( info - > prev_cable_type ) for handling cable when cable is
* detached .
*/
if ( adc = = MAX8997_MUIC_ADC_OPEN ) {
* attached = false ;
cable_type = info - > prev_cable_type ;
info - > prev_cable_type = MAX8997_MUIC_ADC_OPEN ;
} else {
* attached = true ;
cable_type = info - > prev_cable_type = adc ;
}
break ;
case MAX8997_CABLE_GROUP_CHG :
/*
* Read charger type to check cable type and decide cable state
* according to type of charger cable .
*/
chg_type = info - > status [ 1 ] & STATUS2_CHGTYP_MASK ;
chg_type > > = STATUS2_CHGTYP_SHIFT ;
if ( chg_type = = MAX8997_CHARGER_TYPE_NONE ) {
* attached = false ;
cable_type = info - > prev_chg_type ;
info - > prev_chg_type = MAX8997_CHARGER_TYPE_NONE ;
} else {
* attached = true ;
/*
* Check current cable state / cable type and store cable
* type ( info - > prev_chg_type ) for handling cable when
* charger cable is detached .
*/
cable_type = info - > prev_chg_type = chg_type ;
}
break ;
default :
dev_err ( info - > dev , " Unknown cable group (%d) \n " , group ) ;
cable_type = - EINVAL ;
break ;
}
return cable_type ;
}
2011-11-24 13:12:18 +04:00
static int max8997_muic_handle_usb ( struct max8997_muic_info * info ,
enum max8997_muic_usb_type usb_type , bool attached )
{
int ret = 0 ;
if ( usb_type = = MAX8997_USB_HOST ) {
2013-02-13 10:04:15 +04:00
ret = max8997_muic_set_path ( info , info - > path_usb , attached ) ;
2013-02-13 07:05:42 +04:00
if ( ret < 0 ) {
2011-11-24 13:12:18 +04:00
dev_err ( info - > dev , " failed to update muic register \n " ) ;
2013-02-13 07:05:42 +04:00
return ret ;
2011-11-24 13:12:18 +04:00
}
}
2012-05-09 07:31:58 +04:00
switch ( usb_type ) {
case MAX8997_USB_HOST :
extcon_set_cable_state ( info - > edev , " USB-Host " , attached ) ;
break ;
case MAX8997_USB_DEVICE :
extcon_set_cable_state ( info - > edev , " USB " , attached ) ;
break ;
default :
2013-02-13 07:05:42 +04:00
dev_err ( info - > dev , " failed to detect %s usb cable \n " ,
attached ? " attached " : " detached " ) ;
return - EINVAL ;
2012-05-09 07:31:58 +04:00
}
2013-02-13 07:05:42 +04:00
return 0 ;
2011-11-24 13:12:18 +04:00
}
static int max8997_muic_handle_dock ( struct max8997_muic_info * info ,
2013-02-13 07:05:42 +04:00
int cable_type , bool attached )
2011-11-24 13:12:18 +04:00
{
int ret = 0 ;
2013-02-13 03:42:37 +04:00
ret = max8997_muic_set_path ( info , CONTROL1_SW_AUDIO , attached ) ;
2011-11-24 13:12:18 +04:00
if ( ret ) {
dev_err ( info - > dev , " failed to update muic register \n " ) ;
2013-02-13 07:05:42 +04:00
return ret ;
2011-11-24 13:12:18 +04:00
}
2013-02-13 07:05:42 +04:00
switch ( cable_type ) {
case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD :
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " Dock-desk " , attached ) ;
2011-11-24 13:12:18 +04:00
break ;
2013-02-13 07:05:42 +04:00
case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON :
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " Dock-card " , attached ) ;
2011-11-24 13:12:18 +04:00
break ;
default :
2013-02-13 07:05:42 +04:00
dev_err ( info - > dev , " failed to detect %s dock device \n " ,
attached ? " attached " : " detached " ) ;
return - EINVAL ;
2011-11-24 13:12:18 +04:00
}
2013-02-13 07:05:42 +04:00
return 0 ;
2011-11-24 13:12:18 +04:00
}
static int max8997_muic_handle_jig_uart ( struct max8997_muic_info * info ,
bool attached )
{
int ret = 0 ;
/* switch to UART */
2013-02-13 10:04:15 +04:00
ret = max8997_muic_set_path ( info , info - > path_uart , attached ) ;
2011-11-24 13:12:18 +04:00
if ( ret ) {
dev_err ( info - > dev , " failed to update muic register \n " ) ;
2013-04-08 04:13:18 +04:00
return ret ;
2011-11-24 13:12:18 +04:00
}
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " JIG " , attached ) ;
2011-11-24 13:12:18 +04:00
2013-02-13 07:05:42 +04:00
return 0 ;
2011-11-24 13:12:18 +04:00
}
2013-02-13 07:05:42 +04:00
static int max8997_muic_adc_handler ( struct max8997_muic_info * info )
2011-11-24 13:12:18 +04:00
{
2013-02-13 07:05:42 +04:00
int cable_type ;
bool attached ;
2011-11-24 13:12:18 +04:00
int ret = 0 ;
2013-02-13 07:05:42 +04:00
/* Check cable state which is either detached or attached */
cable_type = max8997_muic_get_cable_type ( info ,
MAX8997_CABLE_GROUP_ADC , & attached ) ;
switch ( cable_type ) {
case MAX8997_MUIC_ADC_GROUND :
ret = max8997_muic_handle_usb ( info , MAX8997_USB_HOST , attached ) ;
if ( ret < 0 )
return ret ;
break ;
case MAX8997_MUIC_ADC_MHL :
extcon_set_cable_state ( info - > edev , " MHL " , attached ) ;
break ;
case MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF :
case MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON :
2013-08-23 05:21:37 +04:00
ret = max8997_muic_handle_usb ( info ,
MAX8997_USB_DEVICE , attached ) ;
2013-02-13 07:05:42 +04:00
if ( ret < 0 )
return ret ;
break ;
case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD :
case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON :
ret = max8997_muic_handle_dock ( info , cable_type , attached ) ;
if ( ret < 0 )
return ret ;
break ;
case MAX8997_MUIC_ADC_FACTORY_MODE_UART_OFF :
ret = max8997_muic_handle_jig_uart ( info , attached ) ;
break ;
case MAX8997_MUIC_ADC_REMOTE_S1_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S2_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S3_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S4_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S5_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S6_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S7_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S8_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S9_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S10_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S11_BUTTON :
case MAX8997_MUIC_ADC_REMOTE_S12_BUTTON :
case MAX8997_MUIC_ADC_RESERVED_ACC_1 :
case MAX8997_MUIC_ADC_RESERVED_ACC_2 :
case MAX8997_MUIC_ADC_RESERVED_ACC_3 :
case MAX8997_MUIC_ADC_RESERVED_ACC_4 :
case MAX8997_MUIC_ADC_RESERVED_ACC_5 :
case MAX8997_MUIC_ADC_CEA936_AUDIO :
case MAX8997_MUIC_ADC_PHONE_POWERED_DEV :
case MAX8997_MUIC_ADC_TTY_CONVERTER :
case MAX8997_MUIC_ADC_UART_CABLE :
case MAX8997_MUIC_ADC_CEA936A_TYPE1_CHG :
case MAX8997_MUIC_ADC_CEA936A_TYPE2_CHG :
case MAX8997_MUIC_ADC_AUDIO_MODE_REMOTE :
/*
* This cable isn ' t used in general case if it is specially
* needed to detect additional cable , should implement
* proper operation when this cable is attached / detached .
*/
dev_info ( info - > dev ,
" cable is %s but it isn't used (type:0x%x) \n " ,
attached ? " attached " : " detached " , cable_type ) ;
return - EAGAIN ;
2012-05-09 07:31:58 +04:00
default :
2013-02-13 07:05:42 +04:00
dev_err ( info - > dev ,
" failed to detect %s unknown cable (type:0x%x) \n " ,
attached ? " attached " : " detached " , cable_type ) ;
2012-08-31 12:30:19 +04:00
return - EINVAL ;
2012-05-09 07:31:58 +04:00
}
2011-11-24 13:12:18 +04:00
2012-08-31 12:30:19 +04:00
return 0 ;
2011-11-24 13:12:18 +04:00
}
2013-02-13 07:05:42 +04:00
static int max8997_muic_chg_handler ( struct max8997_muic_info * info )
2011-11-24 13:12:18 +04:00
{
2013-02-13 07:05:42 +04:00
int chg_type ;
bool attached ;
int adc ;
2011-11-24 13:12:18 +04:00
2013-02-13 07:05:42 +04:00
chg_type = max8997_muic_get_cable_type ( info ,
MAX8997_CABLE_GROUP_CHG , & attached ) ;
2011-11-24 13:12:18 +04:00
2013-02-13 07:05:42 +04:00
switch ( chg_type ) {
2011-11-24 13:12:18 +04:00
case MAX8997_CHARGER_TYPE_NONE :
break ;
case MAX8997_CHARGER_TYPE_USB :
2013-02-13 07:05:42 +04:00
adc = info - > status [ 0 ] & STATUS1_ADC_MASK ;
adc > > = STATUS1_ADC_SHIFT ;
if ( ( adc & STATUS1_ADC_MASK ) = = MAX8997_MUIC_ADC_OPEN ) {
2011-11-24 13:12:18 +04:00
max8997_muic_handle_usb ( info ,
2013-02-13 07:05:42 +04:00
MAX8997_USB_DEVICE , attached ) ;
2011-11-24 13:12:18 +04:00
}
break ;
case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT :
2013-08-23 05:21:37 +04:00
extcon_set_cable_state ( info - > edev ,
" Charge-downstream " , attached ) ;
2012-05-09 07:31:58 +04:00
break ;
2011-11-24 13:12:18 +04:00
case MAX8997_CHARGER_TYPE_DEDICATED_CHG :
2013-02-13 07:05:42 +04:00
extcon_set_cable_state ( info - > edev , " TA " , attached ) ;
2012-05-09 07:31:58 +04:00
break ;
2011-11-24 13:12:18 +04:00
case MAX8997_CHARGER_TYPE_500MA :
2013-02-13 07:05:42 +04:00
extcon_set_cable_state ( info - > edev , " Slow-charger " , attached ) ;
2012-05-09 07:31:58 +04:00
break ;
2011-11-24 13:12:18 +04:00
case MAX8997_CHARGER_TYPE_1A :
2013-02-13 07:05:42 +04:00
extcon_set_cable_state ( info - > edev , " Fast-charger " , attached ) ;
2011-11-24 13:12:18 +04:00
break ;
default :
2013-02-13 07:05:42 +04:00
dev_err ( info - > dev ,
" failed to detect %s unknown chg cable (type:0x%x) \n " ,
attached ? " attached " : " detached " , chg_type ) ;
return - EINVAL ;
2011-11-24 13:12:18 +04:00
}
2013-02-13 07:05:42 +04:00
return 0 ;
2011-11-24 13:12:18 +04:00
}
static void max8997_muic_irq_work ( struct work_struct * work )
{
struct max8997_muic_info * info = container_of ( work ,
struct max8997_muic_info , irq_work ) ;
2012-07-02 04:03:00 +04:00
int irq_type = 0 ;
int i , ret ;
2011-11-24 13:12:18 +04:00
2013-02-13 07:05:42 +04:00
if ( ! info - > edev )
return ;
2011-11-24 13:12:18 +04:00
mutex_lock ( & info - > mutex ) ;
2013-08-05 13:02:03 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( muic_irqs ) ; i + + )
2013-02-13 07:05:42 +04:00
if ( info - > irq = = muic_irqs [ i ] . virq )
irq_type = muic_irqs [ i ] . irq ;
2011-11-24 13:12:18 +04:00
ret = max8997_bulk_read ( info - > muic , MAX8997_MUIC_REG_STATUS1 ,
2013-02-13 07:05:42 +04:00
2 , info - > status ) ;
2011-11-24 13:12:18 +04:00
if ( ret ) {
dev_err ( info - > dev , " failed to read muic register \n " ) ;
mutex_unlock ( & info - > mutex ) ;
return ;
}
switch ( irq_type ) {
2013-02-13 07:05:42 +04:00
case MAX8997_MUICIRQ_ADCError :
case MAX8997_MUICIRQ_ADCLow :
2011-11-24 13:12:18 +04:00
case MAX8997_MUICIRQ_ADC :
2013-02-13 07:05:42 +04:00
/* Handle all of cable except for charger cable */
ret = max8997_muic_adc_handler ( info ) ;
2011-11-24 13:12:18 +04:00
break ;
2013-02-13 07:05:42 +04:00
case MAX8997_MUICIRQ_VBVolt :
case MAX8997_MUICIRQ_DBChg :
case MAX8997_MUICIRQ_DCDTmr :
case MAX8997_MUICIRQ_ChgDetRun :
2011-11-24 13:12:18 +04:00
case MAX8997_MUICIRQ_ChgTyp :
2013-02-13 07:05:42 +04:00
/* Handle charger cable */
ret = max8997_muic_chg_handler ( info ) ;
break ;
case MAX8997_MUICIRQ_OVP :
2011-11-24 13:12:18 +04:00
break ;
default :
2012-05-09 07:31:58 +04:00
dev_info ( info - > dev , " misc interrupt: irq %d occurred \n " ,
irq_type ) ;
2013-02-13 07:05:42 +04:00
mutex_unlock ( & info - > mutex ) ;
return ;
2011-11-24 13:12:18 +04:00
}
2013-02-13 07:05:42 +04:00
if ( ret < 0 )
dev_err ( info - > dev , " failed to handle MUIC interrupt \n " ) ;
2011-11-24 13:12:18 +04:00
mutex_unlock ( & info - > mutex ) ;
return ;
}
static irqreturn_t max8997_muic_irq_handler ( int irq , void * data )
{
struct max8997_muic_info * info = data ;
dev_dbg ( info - > dev , " irq:%d \n " , irq ) ;
info - > irq = irq ;
schedule_work ( & info - > irq_work ) ;
return IRQ_HANDLED ;
}
2013-02-13 07:05:42 +04:00
static int max8997_muic_detect_dev ( struct max8997_muic_info * info )
2011-11-24 13:12:18 +04:00
{
2013-02-13 07:05:42 +04:00
int ret = 0 ;
int adc ;
int chg_type ;
bool attached ;
2011-11-24 13:12:18 +04:00
2013-02-13 07:05:42 +04:00
mutex_lock ( & info - > mutex ) ;
/* Read STATUSx register to detect accessory */
ret = max8997_bulk_read ( info - > muic ,
MAX8997_MUIC_REG_STATUS1 , 2 , info - > status ) ;
2011-11-24 13:12:18 +04:00
if ( ret ) {
2013-02-13 07:05:42 +04:00
dev_err ( info - > dev , " failed to read MUIC register \n " ) ;
mutex_unlock ( & info - > mutex ) ;
2013-04-08 04:13:18 +04:00
return ret ;
2011-11-24 13:12:18 +04:00
}
2013-02-13 07:05:42 +04:00
adc = max8997_muic_get_cable_type ( info , MAX8997_CABLE_GROUP_ADC ,
& attached ) ;
if ( attached & & adc ! = MAX8997_MUIC_ADC_OPEN ) {
ret = max8997_muic_adc_handler ( info ) ;
if ( ret < 0 ) {
dev_err ( info - > dev , " Cannot detect ADC cable \n " ) ;
mutex_unlock ( & info - > mutex ) ;
return ret ;
}
}
2011-11-24 13:12:18 +04:00
2013-02-13 07:05:42 +04:00
chg_type = max8997_muic_get_cable_type ( info , MAX8997_CABLE_GROUP_CHG ,
& attached ) ;
if ( attached & & chg_type ! = MAX8997_CHARGER_TYPE_NONE ) {
ret = max8997_muic_chg_handler ( info ) ;
if ( ret < 0 ) {
dev_err ( info - > dev , " Cannot detect charger cable \n " ) ;
mutex_unlock ( & info - > mutex ) ;
return ret ;
}
}
2011-11-24 13:12:18 +04:00
2013-02-13 07:05:42 +04:00
mutex_unlock ( & info - > mutex ) ;
2011-11-24 13:12:18 +04:00
2013-02-13 07:05:42 +04:00
return 0 ;
2011-11-24 13:12:18 +04:00
}
2013-02-13 10:10:00 +04:00
static void max8997_muic_detect_cable_wq ( struct work_struct * work )
{
struct max8997_muic_info * info = container_of ( to_delayed_work ( work ) ,
struct max8997_muic_info , wq_detcable ) ;
int ret ;
ret = max8997_muic_detect_dev ( info ) ;
if ( ret < 0 )
2013-04-08 04:11:06 +04:00
dev_err ( info - > dev , " failed to detect cable type \n " ) ;
2013-02-13 10:10:00 +04:00
}
2012-11-19 22:23:21 +04:00
static int max8997_muic_probe ( struct platform_device * pdev )
2011-11-24 13:12:18 +04:00
{
2012-05-09 07:31:58 +04:00
struct max8997_dev * max8997 = dev_get_drvdata ( pdev - > dev . parent ) ;
struct max8997_platform_data * pdata = dev_get_platdata ( max8997 - > dev ) ;
2011-11-24 13:12:18 +04:00
struct max8997_muic_info * info ;
2013-02-13 10:10:00 +04:00
int delay_jiffies ;
2011-11-24 13:12:18 +04:00
int ret , i ;
2012-11-20 10:46:47 +04:00
info = devm_kzalloc ( & pdev - > dev , sizeof ( struct max8997_muic_info ) ,
GFP_KERNEL ) ;
2011-11-24 13:12:18 +04:00
if ( ! info ) {
dev_err ( & pdev - > dev , " failed to allocate memory \n " ) ;
2012-11-20 10:46:47 +04:00
return - ENOMEM ;
2011-11-24 13:12:18 +04:00
}
info - > dev = & pdev - > dev ;
2012-05-09 07:31:58 +04:00
info - > muic = max8997 - > muic ;
2011-11-24 13:12:18 +04:00
platform_set_drvdata ( pdev , info ) ;
mutex_init ( & info - > mutex ) ;
INIT_WORK ( & info - > irq_work , max8997_muic_irq_work ) ;
for ( i = 0 ; i < ARRAY_SIZE ( muic_irqs ) ; i + + ) {
struct max8997_muic_irq * muic_irq = & muic_irqs [ i ] ;
2012-11-20 10:46:41 +04:00
unsigned int virq = 0 ;
2012-07-02 04:03:00 +04:00
virq = irq_create_mapping ( max8997 - > irq_domain , muic_irq - > irq ) ;
2012-11-20 10:46:41 +04:00
if ( ! virq ) {
ret = - EINVAL ;
2012-07-02 04:03:00 +04:00
goto err_irq ;
2012-11-20 10:46:41 +04:00
}
2012-07-02 04:03:00 +04:00
muic_irq - > virq = virq ;
2011-11-24 13:12:18 +04:00
2012-11-28 07:39:01 +04:00
ret = request_threaded_irq ( virq , NULL ,
max8997_muic_irq_handler ,
IRQF_NO_SUSPEND ,
muic_irq - > name , info ) ;
2011-11-24 13:12:18 +04:00
if ( ret ) {
dev_err ( & pdev - > dev ,
" failed: irq request (IRQ: %d, "
" error :%d) \n " ,
muic_irq - > irq , ret ) ;
goto err_irq ;
}
}
2012-05-09 07:31:58 +04:00
/* External connector */
2012-11-20 10:46:47 +04:00
info - > edev = devm_kzalloc ( & pdev - > dev , sizeof ( struct extcon_dev ) ,
GFP_KERNEL ) ;
2012-05-09 07:31:58 +04:00
if ( ! info - > edev ) {
dev_err ( & pdev - > dev , " failed to allocate memory for extcon \n " ) ;
ret = - ENOMEM ;
goto err_irq ;
}
info - > edev - > name = DEV_NAME ;
2013-09-27 04:20:26 +04:00
info - > edev - > dev . parent = & pdev - > dev ;
2012-05-09 07:31:58 +04:00
info - > edev - > supported_cable = max8997_extcon_cable ;
2013-09-27 04:20:26 +04:00
ret = extcon_dev_register ( info - > edev ) ;
2012-05-09 07:31:58 +04:00
if ( ret ) {
dev_err ( & pdev - > dev , " failed to register extcon device \n " ) ;
2012-11-20 10:46:47 +04:00
goto err_irq ;
2012-05-09 07:31:58 +04:00
}
if ( pdata - > muic_pdata ) {
2013-02-18 05:15:03 +04:00
struct max8997_muic_platform_data * muic_pdata
= pdata - > muic_pdata ;
/* Initialize registers according to platform data */
for ( i = 0 ; i < muic_pdata - > num_init_data ; i + + ) {
max8997_write_reg ( info - > muic ,
muic_pdata - > init_data [ i ] . addr ,
muic_pdata - > init_data [ i ] . data ) ;
2012-05-09 07:31:58 +04:00
}
2011-11-24 13:12:18 +04:00
2013-02-18 05:15:03 +04:00
/*
* Default usb / uart path whether UART / USB or AUX_UART / AUX_USB
* h / w path of COMP2 / COMN1 on CONTROL1 register .
*/
if ( muic_pdata - > path_uart )
info - > path_uart = muic_pdata - > path_uart ;
else
info - > path_uart = CONTROL1_SW_UART ;
2013-02-13 10:04:15 +04:00
2013-02-18 05:15:03 +04:00
if ( muic_pdata - > path_usb )
info - > path_usb = muic_pdata - > path_usb ;
else
info - > path_usb = CONTROL1_SW_USB ;
/*
* Default delay time for detecting cable state
* after certain time .
*/
if ( muic_pdata - > detcable_delay_ms )
delay_jiffies =
msecs_to_jiffies ( muic_pdata - > detcable_delay_ms ) ;
else
delay_jiffies = msecs_to_jiffies ( DELAY_MS_DEFAULT ) ;
} else {
info - > path_uart = CONTROL1_SW_UART ;
2013-02-13 10:04:15 +04:00
info - > path_usb = CONTROL1_SW_USB ;
2013-02-18 05:15:03 +04:00
delay_jiffies = msecs_to_jiffies ( DELAY_MS_DEFAULT ) ;
}
2013-02-13 10:04:15 +04:00
/* Set initial path for UART */
max8997_muic_set_path ( info , info - > path_uart , true ) ;
2013-02-13 03:50:00 +04:00
/* Set ADC debounce time */
max8997_muic_set_debounce_time ( info , ADC_DEBOUNCE_TIME_25MS ) ;
2013-02-13 10:10:00 +04:00
/*
* Detect accessory after completing the initialization of platform
*
* - Use delayed workqueue to detect cable state and then
* notify cable state to notifiee / platform through uevent .
* After completing the booting of platform , the extcon provider
* driver should notify cable state to upper layer .
*/
INIT_DELAYED_WORK ( & info - > wq_detcable , max8997_muic_detect_cable_wq ) ;
schedule_delayed_work ( & info - > wq_detcable , delay_jiffies ) ;
2011-11-24 13:12:18 +04:00
2013-02-13 10:10:00 +04:00
return 0 ;
2011-11-24 13:12:18 +04:00
err_irq :
2012-03-26 05:57:08 +04:00
while ( - - i > = 0 )
2012-07-02 04:03:00 +04:00
free_irq ( muic_irqs [ i ] . virq , info ) ;
2011-11-24 13:12:18 +04:00
return ret ;
}
2012-11-19 22:25:49 +04:00
static int max8997_muic_remove ( struct platform_device * pdev )
2011-11-24 13:12:18 +04:00
{
struct max8997_muic_info * info = platform_get_drvdata ( pdev ) ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( muic_irqs ) ; i + + )
2012-07-02 04:03:00 +04:00
free_irq ( muic_irqs [ i ] . virq , info ) ;
2011-12-15 13:20:47 +04:00
cancel_work_sync ( & info - > irq_work ) ;
2011-11-24 13:12:18 +04:00
2012-05-09 07:31:58 +04:00
extcon_dev_unregister ( info - > edev ) ;
2011-11-24 13:12:18 +04:00
return 0 ;
}
static struct platform_driver max8997_muic_driver = {
. driver = {
2012-05-09 07:31:58 +04:00
. name = DEV_NAME ,
2011-11-24 13:12:18 +04:00
. owner = THIS_MODULE ,
} ,
. probe = max8997_muic_probe ,
2012-11-19 22:20:06 +04:00
. remove = max8997_muic_remove ,
2011-11-24 13:12:18 +04:00
} ;
2012-01-22 11:33:49 +04:00
module_platform_driver ( max8997_muic_driver ) ;
2011-11-24 13:12:18 +04:00
2012-05-09 07:31:58 +04:00
MODULE_DESCRIPTION ( " Maxim MAX8997 Extcon driver " ) ;
2011-11-24 13:12:18 +04:00
MODULE_AUTHOR ( " Donggeun Kim <dg77.kim@samsung.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;