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"
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 [ ] = {
{ MAX8997_MUICIRQ_ADCError , " muic-ADC_error " } ,
{ MAX8997_MUICIRQ_ADCLow , " muic-ADC_low " } ,
{ MAX8997_MUICIRQ_ADC , " muic-ADC " } ,
{ MAX8997_MUICIRQ_VBVolt , " muic-VB_voltage " } ,
{ MAX8997_MUICIRQ_DBChg , " muic-DB_charger " } ,
{ MAX8997_MUICIRQ_DCDTmr , " muic-DCD_timer " } ,
{ MAX8997_MUICIRQ_ChgDetRun , " muic-CDR_status " } ,
{ MAX8997_MUICIRQ_ChgTyp , " muic-charger_type " } ,
{ MAX8997_MUICIRQ_OVP , " muic-over_voltage " } ,
} ;
struct max8997_muic_info {
struct device * dev ;
struct i2c_client * muic ;
struct max8997_muic_platform_data * muic_pdata ;
int irq ;
struct work_struct irq_work ;
enum max8997_muic_charger_type pre_charger_type ;
int pre_adc ;
struct mutex mutex ;
2012-05-09 07:31:58 +04:00
struct extcon_dev * edev ;
} ;
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
} ;
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 ) {
/* switch to USB */
ret = max8997_update_reg ( info - > muic , MAX8997_MUIC_REG_CONTROL1 ,
2013-02-12 15:44:19 +04:00
attached ? CONTROL1_SW_USB : CONTROL1_SW_OPEN ,
CONTROL1_SW_MASK ) ;
2011-11-24 13:12:18 +04:00
if ( ret ) {
dev_err ( info - > dev , " failed to update muic register \n " ) ;
goto out ;
}
}
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 :
ret = - EINVAL ;
break ;
}
2011-11-24 13:12:18 +04:00
out :
return ret ;
}
static int max8997_muic_handle_dock ( struct max8997_muic_info * info ,
int adc , bool attached )
{
int ret = 0 ;
/* switch to AUDIO */
ret = max8997_update_reg ( info - > muic , MAX8997_MUIC_REG_CONTROL1 ,
2013-02-12 15:44:19 +04:00
attached ? CONTROL1_SW_AUDIO : CONTROL1_SW_OPEN ,
CONTROL1_SW_MASK ) ;
2011-11-24 13:12:18 +04:00
if ( ret ) {
dev_err ( info - > dev , " failed to update muic register \n " ) ;
goto out ;
}
switch ( adc ) {
case MAX8997_ADC_DESKDOCK :
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 ;
case MAX8997_ADC_CARDOCK :
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 :
2012-05-09 07:31:58 +04:00
ret = - EINVAL ;
2011-11-24 13:12:18 +04:00
break ;
}
out :
return ret ;
}
static int max8997_muic_handle_jig_uart ( struct max8997_muic_info * info ,
bool attached )
{
int ret = 0 ;
/* switch to UART */
ret = max8997_update_reg ( info - > muic , MAX8997_MUIC_REG_CONTROL1 ,
2013-02-12 15:44:19 +04:00
attached ? CONTROL1_SW_UART : CONTROL1_SW_OPEN ,
CONTROL1_SW_MASK ) ;
2011-11-24 13:12:18 +04:00
if ( ret ) {
dev_err ( info - > dev , " failed to update muic register \n " ) ;
goto out ;
}
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " JIG " , attached ) ;
2011-11-24 13:12:18 +04:00
out :
return ret ;
}
static int max8997_muic_handle_adc_detach ( struct max8997_muic_info * info )
{
int ret = 0 ;
switch ( info - > pre_adc ) {
case MAX8997_ADC_GROUND :
ret = max8997_muic_handle_usb ( info , MAX8997_USB_HOST , false ) ;
break ;
case MAX8997_ADC_MHL :
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " MHL " , false ) ;
2011-11-24 13:12:18 +04:00
break ;
case MAX8997_ADC_JIG_USB_1 :
case MAX8997_ADC_JIG_USB_2 :
ret = max8997_muic_handle_usb ( info , MAX8997_USB_DEVICE , false ) ;
break ;
case MAX8997_ADC_DESKDOCK :
case MAX8997_ADC_CARDOCK :
ret = max8997_muic_handle_dock ( info , info - > pre_adc , false ) ;
break ;
case MAX8997_ADC_JIG_UART :
ret = max8997_muic_handle_jig_uart ( info , false ) ;
break ;
default :
break ;
}
return ret ;
}
static int max8997_muic_handle_adc ( struct max8997_muic_info * info , int adc )
{
int ret = 0 ;
switch ( adc ) {
case MAX8997_ADC_GROUND :
ret = max8997_muic_handle_usb ( info , MAX8997_USB_HOST , true ) ;
break ;
case MAX8997_ADC_MHL :
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " MHL " , true ) ;
2011-11-24 13:12:18 +04:00
break ;
case MAX8997_ADC_JIG_USB_1 :
case MAX8997_ADC_JIG_USB_2 :
ret = max8997_muic_handle_usb ( info , MAX8997_USB_DEVICE , true ) ;
break ;
case MAX8997_ADC_DESKDOCK :
case MAX8997_ADC_CARDOCK :
ret = max8997_muic_handle_dock ( info , adc , true ) ;
break ;
case MAX8997_ADC_JIG_UART :
ret = max8997_muic_handle_jig_uart ( info , true ) ;
break ;
case MAX8997_ADC_OPEN :
ret = max8997_muic_handle_adc_detach ( info ) ;
break ;
default :
2012-05-09 07:31:58 +04:00
ret = - EINVAL ;
goto out ;
2011-11-24 13:12:18 +04:00
}
info - > pre_adc = adc ;
2012-05-09 07:31:58 +04:00
out :
return ret ;
}
static int max8997_muic_handle_charger_type_detach (
struct max8997_muic_info * info )
{
switch ( info - > pre_charger_type ) {
case MAX8997_CHARGER_TYPE_USB :
extcon_set_cable_state ( info - > edev , " USB " , false ) ;
break ;
case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT :
extcon_set_cable_state ( info - > edev , " Charge-downstream " , false ) ;
break ;
case MAX8997_CHARGER_TYPE_DEDICATED_CHG :
extcon_set_cable_state ( info - > edev , " TA " , false ) ;
break ;
case MAX8997_CHARGER_TYPE_500MA :
extcon_set_cable_state ( info - > edev , " Slow-charger " , false ) ;
break ;
case MAX8997_CHARGER_TYPE_1A :
extcon_set_cable_state ( info - > edev , " Fast-charger " , false ) ;
break ;
default :
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
}
static int max8997_muic_handle_charger_type ( struct max8997_muic_info * info ,
enum max8997_muic_charger_type charger_type )
{
u8 adc ;
int ret ;
ret = max8997_read_reg ( info - > muic , MAX8997_MUIC_REG_STATUS1 , & adc ) ;
if ( ret ) {
dev_err ( info - > dev , " failed to read muic register \n " ) ;
goto out ;
}
switch ( charger_type ) {
case MAX8997_CHARGER_TYPE_NONE :
2012-05-09 07:31:58 +04:00
ret = max8997_muic_handle_charger_type_detach ( info ) ;
2011-11-24 13:12:18 +04:00
break ;
case MAX8997_CHARGER_TYPE_USB :
if ( ( adc & STATUS1_ADC_MASK ) = = MAX8997_ADC_OPEN ) {
max8997_muic_handle_usb ( info ,
MAX8997_USB_DEVICE , true ) ;
}
break ;
case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT :
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " Charge-downstream " , true ) ;
break ;
2011-11-24 13:12:18 +04:00
case MAX8997_CHARGER_TYPE_DEDICATED_CHG :
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " TA " , true ) ;
break ;
2011-11-24 13:12:18 +04:00
case MAX8997_CHARGER_TYPE_500MA :
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " Slow-charger " , true ) ;
break ;
2011-11-24 13:12:18 +04:00
case MAX8997_CHARGER_TYPE_1A :
2012-05-09 07:31:58 +04:00
extcon_set_cable_state ( info - > edev , " Fast-charger " , true ) ;
2011-11-24 13:12:18 +04:00
break ;
default :
2012-05-09 07:31:58 +04:00
ret = - EINVAL ;
goto out ;
2011-11-24 13:12:18 +04:00
}
info - > pre_charger_type = charger_type ;
out :
return ret ;
}
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-05-09 07:31:58 +04:00
u8 status [ 2 ] ;
2011-12-15 13:20:47 +04:00
u8 adc , chg_type ;
2012-07-02 04:03:00 +04:00
int irq_type = 0 ;
int i , ret ;
2011-11-24 13:12:18 +04:00
mutex_lock ( & info - > mutex ) ;
ret = max8997_bulk_read ( info - > muic , MAX8997_MUIC_REG_STATUS1 ,
2012-05-09 07:31:58 +04:00
2 , 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 ;
}
dev_dbg ( info - > dev , " %s: STATUS1:0x%x, 2:0x%x \n " , __func__ ,
status [ 0 ] , status [ 1 ] ) ;
2012-07-02 04:03:00 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( muic_irqs ) ; i + + )
if ( info - > irq = = muic_irqs [ i ] . virq )
irq_type = muic_irqs [ i ] . irq ;
2011-11-24 13:12:18 +04:00
switch ( irq_type ) {
case MAX8997_MUICIRQ_ADC :
adc = status [ 0 ] & STATUS1_ADC_MASK ;
adc > > = STATUS1_ADC_SHIFT ;
max8997_muic_handle_adc ( info , adc ) ;
break ;
case MAX8997_MUICIRQ_ChgTyp :
chg_type = status [ 1 ] & STATUS2_CHGTYP_MASK ;
chg_type > > = STATUS2_CHGTYP_SHIFT ;
max8997_muic_handle_charger_type ( info , chg_type ) ;
break ;
default :
2012-05-09 07:31:58 +04:00
dev_info ( info - > dev , " misc interrupt: irq %d occurred \n " ,
irq_type ) ;
2011-11-24 13:12:18 +04:00
break ;
}
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 ;
}
static void max8997_muic_detect_dev ( struct max8997_muic_info * info )
{
int ret ;
u8 status [ 2 ] , adc , chg_type ;
ret = max8997_bulk_read ( info - > muic , MAX8997_MUIC_REG_STATUS1 ,
2 , status ) ;
if ( ret ) {
dev_err ( info - > dev , " failed to read muic register \n " ) ;
return ;
}
dev_info ( info - > dev , " STATUS1:0x%x, STATUS2:0x%x \n " ,
status [ 0 ] , status [ 1 ] ) ;
adc = status [ 0 ] & STATUS1_ADC_MASK ;
adc > > = STATUS1_ADC_SHIFT ;
chg_type = status [ 1 ] & STATUS2_CHGTYP_MASK ;
chg_type > > = STATUS2_CHGTYP_SHIFT ;
max8997_muic_handle_adc ( info , adc ) ;
max8997_muic_handle_charger_type ( info , chg_type ) ;
}
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 ;
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 ;
info - > edev - > supported_cable = max8997_extcon_cable ;
ret = extcon_dev_register ( info - > edev , NULL ) ;
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
}
2011-11-24 13:12:18 +04:00
/* Initialize registers according to platform data */
2012-05-09 07:31:58 +04:00
if ( pdata - > muic_pdata ) {
struct max8997_muic_platform_data * mdata = info - > muic_pdata ;
for ( i = 0 ; i < mdata - > num_init_data ; i + + ) {
max8997_write_reg ( info - > muic , mdata - > init_data [ i ] . addr ,
mdata - > init_data [ i ] . data ) ;
}
}
2011-11-24 13:12:18 +04:00
/* Initial device detection */
max8997_muic_detect_dev ( info ) ;
return ret ;
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 " ) ;