2021-01-26 12:55:59 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Core support for ATC260x PMICs
*
* Copyright ( C ) 2019 Manivannan Sadhasivam < manivannan . sadhasivam @ linaro . org >
* Copyright ( C ) 2020 Cristian Ciocaltea < cristian . ciocaltea @ gmail . com >
*/
# include <linux/interrupt.h>
# include <linux/mfd/atc260x/core.h>
# include <linux/mfd/core.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/regmap.h>
# define ATC260X_CHIP_REV_MAX 31
struct atc260x_init_regs {
unsigned int cmu_devrst ;
unsigned int cmu_devrst_ints ;
unsigned int ints_msk ;
unsigned int pad_en ;
unsigned int pad_en_extirq ;
} ;
static void regmap_lock_mutex ( void * __mutex )
{
struct mutex * mutex = __mutex ;
/*
* Using regmap within an atomic context ( e . g . accessing a PMIC when
* powering system down ) is normally allowed only if the regmap type
* is MMIO and the regcache type is either REGCACHE_NONE or
* REGCACHE_FLAT . For slow buses like I2C and SPI , the regmap is
* internally protected by a mutex which is acquired non - atomically .
*
* Let ' s improve this by using a customized locking scheme inspired
* from I2C atomic transfer . See i2c_in_atomic_xfer_mode ( ) for a
* starting point .
*/
if ( system_state > SYSTEM_RUNNING & & irqs_disabled ( ) )
mutex_trylock ( mutex ) ;
else
mutex_lock ( mutex ) ;
}
static void regmap_unlock_mutex ( void * __mutex )
{
struct mutex * mutex = __mutex ;
mutex_unlock ( mutex ) ;
}
static const struct regmap_config atc2603c_regmap_config = {
. reg_bits = 8 ,
. val_bits = 16 ,
. max_register = ATC2603C_SADDR ,
. cache_type = REGCACHE_NONE ,
} ;
static const struct regmap_config atc2609a_regmap_config = {
. reg_bits = 8 ,
. val_bits = 16 ,
. max_register = ATC2609A_SADDR ,
. cache_type = REGCACHE_NONE ,
} ;
static const struct regmap_irq atc2603c_regmap_irqs [ ] = {
REGMAP_IRQ_REG ( ATC2603C_IRQ_AUDIO , 0 , ATC2603C_INTS_MSK_AUDIO ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_OV , 0 , ATC2603C_INTS_MSK_OV ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_OC , 0 , ATC2603C_INTS_MSK_OC ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_OT , 0 , ATC2603C_INTS_MSK_OT ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_UV , 0 , ATC2603C_INTS_MSK_UV ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_ALARM , 0 , ATC2603C_INTS_MSK_ALARM ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_ONOFF , 0 , ATC2603C_INTS_MSK_ONOFF ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_SGPIO , 0 , ATC2603C_INTS_MSK_SGPIO ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_IR , 0 , ATC2603C_INTS_MSK_IR ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_REMCON , 0 , ATC2603C_INTS_MSK_REMCON ) ,
REGMAP_IRQ_REG ( ATC2603C_IRQ_POWER_IN , 0 , ATC2603C_INTS_MSK_POWERIN ) ,
} ;
static const struct regmap_irq atc2609a_regmap_irqs [ ] = {
REGMAP_IRQ_REG ( ATC2609A_IRQ_AUDIO , 0 , ATC2609A_INTS_MSK_AUDIO ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_OV , 0 , ATC2609A_INTS_MSK_OV ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_OC , 0 , ATC2609A_INTS_MSK_OC ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_OT , 0 , ATC2609A_INTS_MSK_OT ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_UV , 0 , ATC2609A_INTS_MSK_UV ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_ALARM , 0 , ATC2609A_INTS_MSK_ALARM ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_ONOFF , 0 , ATC2609A_INTS_MSK_ONOFF ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_WKUP , 0 , ATC2609A_INTS_MSK_WKUP ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_IR , 0 , ATC2609A_INTS_MSK_IR ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_REMCON , 0 , ATC2609A_INTS_MSK_REMCON ) ,
REGMAP_IRQ_REG ( ATC2609A_IRQ_POWER_IN , 0 , ATC2609A_INTS_MSK_POWERIN ) ,
} ;
static const struct regmap_irq_chip atc2603c_regmap_irq_chip = {
. name = " atc2603c " ,
. irqs = atc2603c_regmap_irqs ,
. num_irqs = ARRAY_SIZE ( atc2603c_regmap_irqs ) ,
. num_regs = 1 ,
. status_base = ATC2603C_INTS_PD ,
2022-11-12 18:18:19 +03:00
. unmask_base = ATC2603C_INTS_MSK ,
2021-01-26 12:55:59 +03:00
} ;
static const struct regmap_irq_chip atc2609a_regmap_irq_chip = {
. name = " atc2609a " ,
. irqs = atc2609a_regmap_irqs ,
. num_irqs = ARRAY_SIZE ( atc2609a_regmap_irqs ) ,
. num_regs = 1 ,
. status_base = ATC2609A_INTS_PD ,
2022-11-12 18:18:19 +03:00
. unmask_base = ATC2609A_INTS_MSK ,
2021-01-26 12:55:59 +03:00
} ;
static const struct resource atc2603c_onkey_resources [ ] = {
DEFINE_RES_IRQ ( ATC2603C_IRQ_ONOFF ) ,
} ;
static const struct resource atc2609a_onkey_resources [ ] = {
DEFINE_RES_IRQ ( ATC2609A_IRQ_ONOFF ) ,
} ;
static const struct mfd_cell atc2603c_mfd_cells [ ] = {
{ . name = " atc260x-regulator " } ,
{ . name = " atc260x-pwrc " } ,
{
. name = " atc260x-onkey " ,
. num_resources = ARRAY_SIZE ( atc2603c_onkey_resources ) ,
. resources = atc2603c_onkey_resources ,
} ,
} ;
static const struct mfd_cell atc2609a_mfd_cells [ ] = {
{ . name = " atc260x-regulator " } ,
{ . name = " atc260x-pwrc " } ,
{
. name = " atc260x-onkey " ,
. num_resources = ARRAY_SIZE ( atc2609a_onkey_resources ) ,
. resources = atc2609a_onkey_resources ,
} ,
} ;
static const struct atc260x_init_regs atc2603c_init_regs = {
. cmu_devrst = ATC2603C_CMU_DEVRST ,
. cmu_devrst_ints = ATC2603C_CMU_DEVRST_INTS ,
. ints_msk = ATC2603C_INTS_MSK ,
. pad_en = ATC2603C_PAD_EN ,
. pad_en_extirq = ATC2603C_PAD_EN_EXTIRQ ,
} ;
static const struct atc260x_init_regs atc2609a_init_regs = {
. cmu_devrst = ATC2609A_CMU_DEVRST ,
. cmu_devrst_ints = ATC2609A_CMU_DEVRST_INTS ,
. ints_msk = ATC2609A_INTS_MSK ,
. pad_en = ATC2609A_PAD_EN ,
. pad_en_extirq = ATC2609A_PAD_EN_EXTIRQ ,
} ;
static void atc260x_cmu_reset ( struct atc260x * atc260x )
{
const struct atc260x_init_regs * regs = atc260x - > init_regs ;
/* Assert reset */
regmap_update_bits ( atc260x - > regmap , regs - > cmu_devrst ,
regs - > cmu_devrst_ints , ~ regs - > cmu_devrst_ints ) ;
/* De-assert reset */
regmap_update_bits ( atc260x - > regmap , regs - > cmu_devrst ,
regs - > cmu_devrst_ints , regs - > cmu_devrst_ints ) ;
}
static void atc260x_dev_init ( struct atc260x * atc260x )
{
const struct atc260x_init_regs * regs = atc260x - > init_regs ;
/* Initialize interrupt block */
atc260x_cmu_reset ( atc260x ) ;
/* Disable all interrupt sources */
regmap_write ( atc260x - > regmap , regs - > ints_msk , 0 ) ;
/* Enable EXTIRQ pad */
regmap_update_bits ( atc260x - > regmap , regs - > pad_en ,
regs - > pad_en_extirq , regs - > pad_en_extirq ) ;
}
/**
* atc260x_match_device ( ) : Setup ATC260x variant related fields
*
* @ atc260x : ATC260x device to setup ( . dev field must be set )
* @ regmap_cfg : regmap config associated with this ATC260x device
*
* This lets the ATC260x core configure the MFD cells and register maps
* for later use .
*/
int atc260x_match_device ( struct atc260x * atc260x , struct regmap_config * regmap_cfg )
{
struct device * dev = atc260x - > dev ;
const void * of_data ;
of_data = of_device_get_match_data ( dev ) ;
if ( ! of_data )
return - ENODEV ;
atc260x - > ic_type = ( unsigned long ) of_data ;
switch ( atc260x - > ic_type ) {
case ATC2603C :
* regmap_cfg = atc2603c_regmap_config ;
atc260x - > regmap_irq_chip = & atc2603c_regmap_irq_chip ;
atc260x - > cells = atc2603c_mfd_cells ;
atc260x - > nr_cells = ARRAY_SIZE ( atc2603c_mfd_cells ) ;
atc260x - > type_name = " atc2603c " ;
atc260x - > rev_reg = ATC2603C_CHIP_VER ;
atc260x - > init_regs = & atc2603c_init_regs ;
break ;
case ATC2609A :
* regmap_cfg = atc2609a_regmap_config ;
atc260x - > regmap_irq_chip = & atc2609a_regmap_irq_chip ;
atc260x - > cells = atc2609a_mfd_cells ;
atc260x - > nr_cells = ARRAY_SIZE ( atc2609a_mfd_cells ) ;
atc260x - > type_name = " atc2609a " ;
atc260x - > rev_reg = ATC2609A_CHIP_VER ;
atc260x - > init_regs = & atc2609a_init_regs ;
break ;
default :
dev_err ( dev , " Unsupported ATC260x device type: %u \n " ,
atc260x - > ic_type ) ;
return - EINVAL ;
}
atc260x - > regmap_mutex = devm_kzalloc ( dev , sizeof ( * atc260x - > regmap_mutex ) ,
GFP_KERNEL ) ;
if ( ! atc260x - > regmap_mutex )
return - ENOMEM ;
mutex_init ( atc260x - > regmap_mutex ) ;
regmap_cfg - > lock = regmap_lock_mutex ,
regmap_cfg - > unlock = regmap_unlock_mutex ,
regmap_cfg - > lock_arg = atc260x - > regmap_mutex ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( atc260x_match_device ) ;
/**
* atc260x_device_probe ( ) : Probe a configured ATC260x device
*
* @ atc260x : ATC260x device to probe ( must be configured )
*
* This function lets the ATC260x core register the ATC260x MFD devices
* and IRQCHIP . The ATC260x device passed in must be fully configured
* with atc260x_match_device , its IRQ set , and regmap created .
*/
int atc260x_device_probe ( struct atc260x * atc260x )
{
struct device * dev = atc260x - > dev ;
unsigned int chip_rev ;
int ret ;
if ( ! atc260x - > irq ) {
dev_err ( dev , " No interrupt support \n " ) ;
return - EINVAL ;
}
/* Initialize the hardware */
atc260x_dev_init ( atc260x ) ;
ret = regmap_read ( atc260x - > regmap , atc260x - > rev_reg , & chip_rev ) ;
if ( ret ) {
dev_err ( dev , " Failed to get chip revision \n " ) ;
return ret ;
}
if ( chip_rev > ATC260X_CHIP_REV_MAX ) {
dev_err ( dev , " Unknown chip revision: %u \n " , chip_rev ) ;
return - EINVAL ;
}
atc260x - > ic_ver = __ffs ( chip_rev + 1U ) ;
dev_info ( dev , " Detected chip type %s rev.%c \n " ,
atc260x - > type_name , ' A ' + atc260x - > ic_ver ) ;
ret = devm_regmap_add_irq_chip ( dev , atc260x - > regmap , atc260x - > irq , IRQF_ONESHOT ,
- 1 , atc260x - > regmap_irq_chip , & atc260x - > irq_data ) ;
if ( ret ) {
dev_err ( dev , " Failed to add IRQ chip: %d \n " , ret ) ;
return ret ;
}
ret = devm_mfd_add_devices ( dev , PLATFORM_DEVID_NONE ,
atc260x - > cells , atc260x - > nr_cells , NULL , 0 ,
regmap_irq_get_domain ( atc260x - > irq_data ) ) ;
if ( ret ) {
dev_err ( dev , " Failed to add child devices: %d \n " , ret ) ;
regmap_del_irq_chip ( atc260x - > irq , atc260x - > irq_data ) ;
}
return ret ;
}
EXPORT_SYMBOL_GPL ( atc260x_device_probe ) ;
MODULE_DESCRIPTION ( " ATC260x PMICs Core support " ) ;
MODULE_AUTHOR ( " Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> " ) ;
MODULE_AUTHOR ( " Cristian Ciocaltea <cristian.ciocaltea@gmail.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;