2020-10-15 17:07:34 +03:00
// SPDX-License-Identifier: GPL-2.0
2022-09-06 07:04:29 +03:00
/*
2020-10-15 17:07:34 +03:00
* drivers / extcon / extcon - tusb320 . c - TUSB320 extcon driver
*
* Copyright ( C ) 2020 National Instruments Corporation
* Author : Michael Auchter < michael . auchter @ ni . com >
*/
2022-07-30 21:05:00 +03:00
# include <linux/bitfield.h>
2020-10-15 17:07:34 +03:00
# include <linux/extcon-provider.h>
# include <linux/i2c.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/regmap.h>
2022-07-30 21:05:00 +03:00
# include <linux/usb/typec.h>
# define TUSB320_REG8 0x8
# define TUSB320_REG8_CURRENT_MODE_ADVERTISE GENMASK(7, 6)
# define TUSB320_REG8_CURRENT_MODE_ADVERTISE_USB 0x0
# define TUSB320_REG8_CURRENT_MODE_ADVERTISE_15A 0x1
# define TUSB320_REG8_CURRENT_MODE_ADVERTISE_30A 0x2
# define TUSB320_REG8_CURRENT_MODE_DETECT GENMASK(5, 4)
# define TUSB320_REG8_CURRENT_MODE_DETECT_DEF 0x0
# define TUSB320_REG8_CURRENT_MODE_DETECT_MED 0x1
# define TUSB320_REG8_CURRENT_MODE_DETECT_ACC 0x2
# define TUSB320_REG8_CURRENT_MODE_DETECT_HI 0x3
# define TUSB320_REG8_ACCESSORY_CONNECTED GENMASK(3, 2)
# define TUSB320_REG8_ACCESSORY_CONNECTED_NONE 0x0
# define TUSB320_REG8_ACCESSORY_CONNECTED_AUDIO 0x4
# define TUSB320_REG8_ACCESSORY_CONNECTED_ACC 0x5
# define TUSB320_REG8_ACCESSORY_CONNECTED_DEBUG 0x6
# define TUSB320_REG8_ACTIVE_CABLE_DETECTION BIT(0)
2020-10-15 17:07:34 +03:00
# define TUSB320_REG9 0x9
# define TUSB320_REG9_ATTACHED_STATE_SHIFT 6
# define TUSB320_REG9_ATTACHED_STATE_MASK 0x3
# define TUSB320_REG9_CABLE_DIRECTION BIT(5)
# define TUSB320_REG9_INTERRUPT_STATUS BIT(4)
2021-09-25 08:45:39 +03:00
# define TUSB320_REGA 0xa
2021-09-25 08:45:54 +03:00
# define TUSB320L_REGA_DISABLE_TERM BIT(0)
2021-09-25 08:45:39 +03:00
# define TUSB320_REGA_I2C_SOFT_RESET BIT(3)
# define TUSB320_REGA_MODE_SELECT_SHIFT 4
# define TUSB320_REGA_MODE_SELECT_MASK 0x3
2021-09-25 08:45:54 +03:00
# define TUSB320L_REGA0_REVISION 0xa0
2021-09-25 08:45:39 +03:00
enum tusb320_attached_state {
TUSB320_ATTACHED_STATE_NONE ,
TUSB320_ATTACHED_STATE_DFP ,
TUSB320_ATTACHED_STATE_UFP ,
TUSB320_ATTACHED_STATE_ACC ,
} ;
enum tusb320_mode {
TUSB320_MODE_PORT ,
TUSB320_MODE_UFP ,
TUSB320_MODE_DFP ,
TUSB320_MODE_DRP ,
} ;
2020-10-15 17:07:34 +03:00
2021-09-25 08:45:54 +03:00
struct tusb320_priv ;
struct tusb320_ops {
int ( * set_mode ) ( struct tusb320_priv * priv , enum tusb320_mode mode ) ;
int ( * get_revision ) ( struct tusb320_priv * priv , unsigned int * revision ) ;
} ;
2020-10-15 17:07:34 +03:00
struct tusb320_priv {
struct device * dev ;
struct regmap * regmap ;
struct extcon_dev * edev ;
2021-09-25 08:45:54 +03:00
struct tusb320_ops * ops ;
2021-09-25 08:45:39 +03:00
enum tusb320_attached_state state ;
2022-07-30 21:05:00 +03:00
struct typec_port * port ;
struct typec_capability cap ;
enum typec_port_type port_type ;
enum typec_pwr_opmode pwr_opmode ;
2020-10-15 17:07:34 +03:00
} ;
static const char * const tusb_attached_states [ ] = {
[ TUSB320_ATTACHED_STATE_NONE ] = " not attached " ,
[ TUSB320_ATTACHED_STATE_DFP ] = " downstream facing port " ,
[ TUSB320_ATTACHED_STATE_UFP ] = " upstream facing port " ,
[ TUSB320_ATTACHED_STATE_ACC ] = " accessory " ,
} ;
static const unsigned int tusb320_extcon_cable [ ] = {
EXTCON_USB ,
EXTCON_USB_HOST ,
EXTCON_NONE ,
} ;
static int tusb320_check_signature ( struct tusb320_priv * priv )
{
static const char sig [ ] = { ' \0 ' , ' T ' , ' U ' , ' S ' , ' B ' , ' 3 ' , ' 2 ' , ' 0 ' } ;
unsigned val ;
int i , ret ;
for ( i = 0 ; i < sizeof ( sig ) ; i + + ) {
ret = regmap_read ( priv - > regmap , sizeof ( sig ) - 1 - i , & val ) ;
if ( ret < 0 )
return ret ;
if ( val ! = sig [ i ] ) {
dev_err ( priv - > dev , " signature mismatch! \n " ) ;
return - ENODEV ;
}
}
return 0 ;
}
2021-09-25 08:45:39 +03:00
static int tusb320_set_mode ( struct tusb320_priv * priv , enum tusb320_mode mode )
{
int ret ;
/* Mode cannot be changed while cable is attached */
if ( priv - > state ! = TUSB320_ATTACHED_STATE_NONE )
return - EBUSY ;
/* Write mode */
ret = regmap_write_bits ( priv - > regmap , TUSB320_REGA ,
TUSB320_REGA_MODE_SELECT_MASK < < TUSB320_REGA_MODE_SELECT_SHIFT ,
mode < < TUSB320_REGA_MODE_SELECT_SHIFT ) ;
if ( ret ) {
dev_err ( priv - > dev , " failed to write mode: %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
2021-09-25 08:45:54 +03:00
static int tusb320l_set_mode ( struct tusb320_priv * priv , enum tusb320_mode mode )
{
int ret ;
/* Disable CC state machine */
ret = regmap_write_bits ( priv - > regmap , TUSB320_REGA ,
TUSB320L_REGA_DISABLE_TERM , 1 ) ;
if ( ret ) {
dev_err ( priv - > dev ,
" failed to disable CC state machine: %d \n " , ret ) ;
return ret ;
}
/* Write mode */
ret = regmap_write_bits ( priv - > regmap , TUSB320_REGA ,
TUSB320_REGA_MODE_SELECT_MASK < < TUSB320_REGA_MODE_SELECT_SHIFT ,
mode < < TUSB320_REGA_MODE_SELECT_SHIFT ) ;
if ( ret ) {
dev_err ( priv - > dev , " failed to write mode: %d \n " , ret ) ;
goto err ;
}
msleep ( 5 ) ;
err :
/* Re-enable CC state machine */
ret = regmap_write_bits ( priv - > regmap , TUSB320_REGA ,
TUSB320L_REGA_DISABLE_TERM , 0 ) ;
if ( ret )
dev_err ( priv - > dev ,
" failed to re-enable CC state machine: %d \n " , ret ) ;
return ret ;
}
2021-09-25 08:45:39 +03:00
static int tusb320_reset ( struct tusb320_priv * priv )
{
int ret ;
/* Set mode to default (follow PORT pin) */
2021-09-25 08:45:54 +03:00
ret = priv - > ops - > set_mode ( priv , TUSB320_MODE_PORT ) ;
2021-09-25 08:45:39 +03:00
if ( ret & & ret ! = - EBUSY ) {
dev_err ( priv - > dev ,
" failed to set mode to PORT: %d \n " , ret ) ;
return ret ;
}
/* Perform soft reset */
ret = regmap_write_bits ( priv - > regmap , TUSB320_REGA ,
TUSB320_REGA_I2C_SOFT_RESET , 1 ) ;
if ( ret ) {
dev_err ( priv - > dev ,
" failed to write soft reset bit: %d \n " , ret ) ;
return ret ;
}
/* Wait for chip to go through reset */
msleep ( 95 ) ;
return 0 ;
}
2021-09-25 08:45:54 +03:00
static int tusb320l_get_revision ( struct tusb320_priv * priv , unsigned int * revision )
{
return regmap_read ( priv - > regmap , TUSB320L_REGA0_REVISION , revision ) ;
}
static struct tusb320_ops tusb320_ops = {
. set_mode = tusb320_set_mode ,
} ;
static struct tusb320_ops tusb320l_ops = {
. set_mode = tusb320l_set_mode ,
. get_revision = tusb320l_get_revision ,
} ;
2022-07-30 21:05:00 +03:00
static int tusb320_set_adv_pwr_mode ( struct tusb320_priv * priv )
{
u8 mode ;
if ( priv - > pwr_opmode = = TYPEC_PWR_MODE_USB )
mode = TUSB320_REG8_CURRENT_MODE_ADVERTISE_USB ;
else if ( priv - > pwr_opmode = = TYPEC_PWR_MODE_1_5A )
mode = TUSB320_REG8_CURRENT_MODE_ADVERTISE_15A ;
else if ( priv - > pwr_opmode = = TYPEC_PWR_MODE_3_0A )
mode = TUSB320_REG8_CURRENT_MODE_ADVERTISE_30A ;
else /* No other mode is supported. */
return - EINVAL ;
return regmap_write_bits ( priv - > regmap , TUSB320_REG8 ,
TUSB320_REG8_CURRENT_MODE_ADVERTISE ,
FIELD_PREP ( TUSB320_REG8_CURRENT_MODE_ADVERTISE ,
mode ) ) ;
}
static int tusb320_port_type_set ( struct typec_port * port ,
enum typec_port_type type )
{
struct tusb320_priv * priv = typec_get_drvdata ( port ) ;
if ( type = = TYPEC_PORT_SRC )
return priv - > ops - > set_mode ( priv , TUSB320_MODE_DFP ) ;
else if ( type = = TYPEC_PORT_SNK )
return priv - > ops - > set_mode ( priv , TUSB320_MODE_UFP ) ;
else if ( type = = TYPEC_PORT_DRP )
return priv - > ops - > set_mode ( priv , TUSB320_MODE_DRP ) ;
else
return priv - > ops - > set_mode ( priv , TUSB320_MODE_PORT ) ;
}
static const struct typec_operations tusb320_typec_ops = {
. port_type_set = tusb320_port_type_set ,
} ;
2022-07-30 21:04:59 +03:00
static void tusb320_extcon_irq_handler ( struct tusb320_priv * priv , u8 reg )
2020-10-15 17:07:34 +03:00
{
int state , polarity ;
state = ( reg > > TUSB320_REG9_ATTACHED_STATE_SHIFT ) &
TUSB320_REG9_ATTACHED_STATE_MASK ;
polarity = ! ! ( reg & TUSB320_REG9_CABLE_DIRECTION ) ;
dev_dbg ( priv - > dev , " attached state: %s, polarity: %d \n " ,
tusb_attached_states [ state ] , polarity ) ;
extcon_set_state ( priv - > edev , EXTCON_USB ,
state = = TUSB320_ATTACHED_STATE_UFP ) ;
extcon_set_state ( priv - > edev , EXTCON_USB_HOST ,
state = = TUSB320_ATTACHED_STATE_DFP ) ;
extcon_set_property ( priv - > edev , EXTCON_USB ,
EXTCON_PROP_USB_TYPEC_POLARITY ,
( union extcon_property_value ) polarity ) ;
extcon_set_property ( priv - > edev , EXTCON_USB_HOST ,
EXTCON_PROP_USB_TYPEC_POLARITY ,
( union extcon_property_value ) polarity ) ;
extcon_sync ( priv - > edev , EXTCON_USB ) ;
extcon_sync ( priv - > edev , EXTCON_USB_HOST ) ;
2021-09-25 08:45:39 +03:00
priv - > state = state ;
2022-07-30 21:04:59 +03:00
}
2022-07-30 21:05:00 +03:00
static void tusb320_typec_irq_handler ( struct tusb320_priv * priv , u8 reg9 )
{
struct typec_port * port = priv - > port ;
struct device * dev = priv - > dev ;
u8 mode , role , state ;
int ret , reg8 ;
bool ori ;
ori = reg9 & TUSB320_REG9_CABLE_DIRECTION ;
typec_set_orientation ( port , ori ? TYPEC_ORIENTATION_REVERSE :
TYPEC_ORIENTATION_NORMAL ) ;
state = ( reg9 > > TUSB320_REG9_ATTACHED_STATE_SHIFT ) &
TUSB320_REG9_ATTACHED_STATE_MASK ;
if ( state = = TUSB320_ATTACHED_STATE_DFP )
role = TYPEC_SOURCE ;
else
role = TYPEC_SINK ;
typec_set_vconn_role ( port , role ) ;
typec_set_pwr_role ( port , role ) ;
typec_set_data_role ( port , role = = TYPEC_SOURCE ?
TYPEC_HOST : TYPEC_DEVICE ) ;
ret = regmap_read ( priv - > regmap , TUSB320_REG8 , & reg8 ) ;
if ( ret ) {
dev_err ( dev , " error during reg8 i2c read, ret=%d! \n " , ret ) ;
return ;
}
mode = FIELD_GET ( TUSB320_REG8_CURRENT_MODE_DETECT , reg8 ) ;
if ( mode = = TUSB320_REG8_CURRENT_MODE_DETECT_DEF )
typec_set_pwr_opmode ( port , TYPEC_PWR_MODE_USB ) ;
else if ( mode = = TUSB320_REG8_CURRENT_MODE_DETECT_MED )
typec_set_pwr_opmode ( port , TYPEC_PWR_MODE_1_5A ) ;
else if ( mode = = TUSB320_REG8_CURRENT_MODE_DETECT_HI )
typec_set_pwr_opmode ( port , TYPEC_PWR_MODE_3_0A ) ;
else /* Charge through accessory */
typec_set_pwr_opmode ( port , TYPEC_PWR_MODE_USB ) ;
}
2022-11-20 17:15:09 +03:00
static irqreturn_t tusb320_state_update_handler ( struct tusb320_priv * priv ,
bool force_update )
2022-07-30 21:04:59 +03:00
{
unsigned int reg ;
if ( regmap_read ( priv - > regmap , TUSB320_REG9 , & reg ) ) {
dev_err ( priv - > dev , " error during i2c read! \n " ) ;
return IRQ_NONE ;
}
2022-11-20 17:15:09 +03:00
if ( ! force_update & & ! ( reg & TUSB320_REG9_INTERRUPT_STATUS ) )
2022-07-30 21:04:59 +03:00
return IRQ_NONE ;
tusb320_extcon_irq_handler ( priv , reg ) ;
2022-11-07 18:33:17 +03:00
/*
* Type - C support is optional . Only call the Type - C handler if a
* port had been registered previously .
*/
if ( priv - > port )
tusb320_typec_irq_handler ( priv , reg ) ;
2021-09-25 08:45:39 +03:00
2020-10-15 17:07:34 +03:00
regmap_write ( priv - > regmap , TUSB320_REG9 , reg ) ;
return IRQ_HANDLED ;
}
2022-11-20 17:15:09 +03:00
static irqreturn_t tusb320_irq_handler ( int irq , void * dev_id )
{
struct tusb320_priv * priv = dev_id ;
return tusb320_state_update_handler ( priv , false ) ;
}
2020-10-15 17:07:34 +03:00
static const struct regmap_config tusb320_regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
} ;
2022-07-30 21:04:59 +03:00
static int tusb320_extcon_probe ( struct tusb320_priv * priv )
{
int ret ;
priv - > edev = devm_extcon_dev_allocate ( priv - > dev , tusb320_extcon_cable ) ;
if ( IS_ERR ( priv - > edev ) ) {
dev_err ( priv - > dev , " failed to allocate extcon device \n " ) ;
return PTR_ERR ( priv - > edev ) ;
}
ret = devm_extcon_dev_register ( priv - > dev , priv - > edev ) ;
if ( ret < 0 ) {
dev_err ( priv - > dev , " failed to register extcon device \n " ) ;
return ret ;
}
extcon_set_property_capability ( priv - > edev , EXTCON_USB ,
EXTCON_PROP_USB_TYPEC_POLARITY ) ;
extcon_set_property_capability ( priv - > edev , EXTCON_USB_HOST ,
EXTCON_PROP_USB_TYPEC_POLARITY ) ;
return 0 ;
}
2022-07-30 21:05:00 +03:00
static int tusb320_typec_probe ( struct i2c_client * client ,
struct tusb320_priv * priv )
{
struct fwnode_handle * connector ;
const char * cap_str ;
int ret ;
/* The Type-C connector is optional, for backward compatibility. */
connector = device_get_named_child_node ( & client - > dev , " connector " ) ;
if ( ! connector )
return 0 ;
/* Type-C connector found. */
ret = typec_get_fw_cap ( & priv - > cap , connector ) ;
if ( ret )
return ret ;
priv - > port_type = priv - > cap . type ;
/* This goes into register 0x8 field CURRENT_MODE_ADVERTISE */
ret = fwnode_property_read_string ( connector , " typec-power-opmode " , & cap_str ) ;
if ( ret )
return ret ;
ret = typec_find_pwr_opmode ( cap_str ) ;
if ( ret < 0 )
return ret ;
if ( ret = = TYPEC_PWR_MODE_PD )
return - EINVAL ;
priv - > pwr_opmode = ret ;
/* Initialize the hardware with the devicetree settings. */
ret = tusb320_set_adv_pwr_mode ( priv ) ;
if ( ret )
return ret ;
priv - > cap . revision = USB_TYPEC_REV_1_1 ;
priv - > cap . accessory [ 0 ] = TYPEC_ACCESSORY_AUDIO ;
priv - > cap . accessory [ 1 ] = TYPEC_ACCESSORY_DEBUG ;
priv - > cap . orientation_aware = true ;
priv - > cap . driver_data = priv ;
priv - > cap . ops = & tusb320_typec_ops ;
priv - > cap . fwnode = connector ;
priv - > port = typec_register_port ( & client - > dev , & priv - > cap ) ;
if ( IS_ERR ( priv - > port ) )
return PTR_ERR ( priv - > port ) ;
return 0 ;
}
2022-11-19 01:35:44 +03:00
static int tusb320_probe ( struct i2c_client * client )
2020-10-15 17:07:34 +03:00
{
struct tusb320_priv * priv ;
2021-09-25 08:45:54 +03:00
const void * match_data ;
unsigned int revision ;
2020-10-15 17:07:34 +03:00
int ret ;
priv = devm_kzalloc ( & client - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > dev = & client - > dev ;
priv - > regmap = devm_regmap_init_i2c ( client , & tusb320_regmap_config ) ;
if ( IS_ERR ( priv - > regmap ) )
return PTR_ERR ( priv - > regmap ) ;
ret = tusb320_check_signature ( priv ) ;
if ( ret )
return ret ;
2021-09-25 08:45:54 +03:00
match_data = device_get_match_data ( & client - > dev ) ;
if ( ! match_data )
return - EINVAL ;
priv - > ops = ( struct tusb320_ops * ) match_data ;
if ( priv - > ops - > get_revision ) {
ret = priv - > ops - > get_revision ( priv , & revision ) ;
if ( ret )
dev_warn ( priv - > dev ,
" failed to read revision register: %d \n " , ret ) ;
else
dev_info ( priv - > dev , " chip revision %d \n " , revision ) ;
}
2022-07-30 21:04:59 +03:00
ret = tusb320_extcon_probe ( priv ) ;
if ( ret )
2020-10-15 17:07:34 +03:00
return ret ;
2022-07-30 21:05:00 +03:00
ret = tusb320_typec_probe ( client , priv ) ;
if ( ret )
return ret ;
2020-10-15 17:07:34 +03:00
/* update initial state */
2022-11-20 17:15:09 +03:00
tusb320_state_update_handler ( priv , true ) ;
2020-10-15 17:07:34 +03:00
2021-09-25 08:45:39 +03:00
/* Reset chip to its default state */
ret = tusb320_reset ( priv ) ;
if ( ret )
dev_warn ( priv - > dev , " failed to reset chip: %d \n " , ret ) ;
else
/*
* State and polarity might change after a reset , so update
* them again and make sure the interrupt status bit is cleared .
*/
2022-11-20 17:15:09 +03:00
tusb320_state_update_handler ( priv , true ) ;
2021-09-25 08:45:39 +03:00
2020-10-15 17:07:34 +03:00
ret = devm_request_threaded_irq ( priv - > dev , client - > irq , NULL ,
tusb320_irq_handler ,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT ,
client - > name , priv ) ;
return ret ;
}
static const struct of_device_id tusb320_extcon_dt_match [ ] = {
2021-09-25 08:45:54 +03:00
{ . compatible = " ti,tusb320 " , . data = & tusb320_ops , } ,
{ . compatible = " ti,tusb320l " , . data = & tusb320l_ops , } ,
2020-10-15 17:07:34 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , tusb320_extcon_dt_match ) ;
static struct i2c_driver tusb320_extcon_driver = {
2022-11-19 01:35:44 +03:00
. probe_new = tusb320_probe ,
2020-10-15 17:07:34 +03:00
. driver = {
. name = " extcon-tusb320 " ,
. of_match_table = tusb320_extcon_dt_match ,
} ,
} ;
static int __init tusb320_init ( void )
{
return i2c_add_driver ( & tusb320_extcon_driver ) ;
}
subsys_initcall ( tusb320_init ) ;
static void __exit tusb320_exit ( void )
{
i2c_del_driver ( & tusb320_extcon_driver ) ;
}
module_exit ( tusb320_exit ) ;
MODULE_AUTHOR ( " Michael Auchter <michael.auchter@ni.com> " ) ;
MODULE_DESCRIPTION ( " TI TUSB320 extcon driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;