2015-09-14 19:39:19 +03:00
/*
* MFD core driver for Intel Broxton Whiskey Cove PMIC
*
* Copyright ( C ) 2015 Intel Corporation . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope 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/module.h>
# include <linux/acpi.h>
# include <linux/err.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/mfd/core.h>
# include <linux/mfd/intel_bxtwc.h>
# include <asm/intel_pmc_ipc.h>
/* PMIC device registers */
# define REG_ADDR_MASK 0xFF00
# define REG_ADDR_SHIFT 8
# define REG_OFFSET_MASK 0xFF
/* Interrupt Status Registers */
# define BXTWC_IRQLVL1 0x4E02
# define BXTWC_PWRBTNIRQ 0x4E03
# define BXTWC_THRM0IRQ 0x4E04
# define BXTWC_THRM1IRQ 0x4E05
# define BXTWC_THRM2IRQ 0x4E06
# define BXTWC_BCUIRQ 0x4E07
# define BXTWC_ADCIRQ 0x4E08
# define BXTWC_CHGR0IRQ 0x4E09
# define BXTWC_CHGR1IRQ 0x4E0A
# define BXTWC_GPIOIRQ0 0x4E0B
# define BXTWC_GPIOIRQ1 0x4E0C
# define BXTWC_CRITIRQ 0x4E0D
2016-11-07 23:11:47 +03:00
# define BXTWC_TMUIRQ 0x4FB6
2015-09-14 19:39:19 +03:00
/* Interrupt MASK Registers */
# define BXTWC_MIRQLVL1 0x4E0E
# define BXTWC_MPWRTNIRQ 0x4E0F
2016-07-21 03:33:56 +03:00
# define BXTWC_MIRQLVL1_MCHGR BIT(5)
2015-09-14 19:39:19 +03:00
# define BXTWC_MTHRM0IRQ 0x4E12
# define BXTWC_MTHRM1IRQ 0x4E13
# define BXTWC_MTHRM2IRQ 0x4E14
# define BXTWC_MBCUIRQ 0x4E15
# define BXTWC_MADCIRQ 0x4E16
# define BXTWC_MCHGR0IRQ 0x4E17
# define BXTWC_MCHGR1IRQ 0x4E18
# define BXTWC_MGPIO0IRQ 0x4E19
# define BXTWC_MGPIO1IRQ 0x4E1A
# define BXTWC_MCRITIRQ 0x4E1B
2016-11-07 23:11:47 +03:00
# define BXTWC_MTMUIRQ 0x4FB7
2015-09-14 19:39:19 +03:00
/* Whiskey Cove PMIC share same ACPI ID between different platforms */
# define BROXTON_PMIC_WC_HRV 4
/* Manage in two IRQ chips since mask registers are not consecutive */
enum bxtwc_irqs {
/* Level 1 */
BXTWC_PWRBTN_LVL1_IRQ = 0 ,
BXTWC_TMU_LVL1_IRQ ,
BXTWC_THRM_LVL1_IRQ ,
BXTWC_BCU_LVL1_IRQ ,
BXTWC_ADC_LVL1_IRQ ,
BXTWC_CHGR_LVL1_IRQ ,
BXTWC_GPIO_LVL1_IRQ ,
BXTWC_CRIT_LVL1_IRQ ,
/* Level 2 */
BXTWC_PWRBTN_IRQ ,
} ;
enum bxtwc_irqs_level2 {
/* Level 2 */
BXTWC_THRM0_IRQ = 0 ,
BXTWC_THRM1_IRQ ,
BXTWC_THRM2_IRQ ,
BXTWC_BCU_IRQ ,
BXTWC_ADC_IRQ ,
2016-10-17 10:32:13 +03:00
BXTWC_USBC_IRQ ,
2015-09-14 19:39:19 +03:00
BXTWC_CHGR0_IRQ ,
BXTWC_CHGR1_IRQ ,
BXTWC_GPIO0_IRQ ,
BXTWC_GPIO1_IRQ ,
BXTWC_CRIT_IRQ ,
2016-11-07 23:11:47 +03:00
BXTWC_TMU_IRQ ,
2015-09-14 19:39:19 +03:00
} ;
static const struct regmap_irq bxtwc_regmap_irqs [ ] = {
REGMAP_IRQ_REG ( BXTWC_PWRBTN_LVL1_IRQ , 0 , BIT ( 0 ) ) ,
REGMAP_IRQ_REG ( BXTWC_TMU_LVL1_IRQ , 0 , BIT ( 1 ) ) ,
REGMAP_IRQ_REG ( BXTWC_THRM_LVL1_IRQ , 0 , BIT ( 2 ) ) ,
REGMAP_IRQ_REG ( BXTWC_BCU_LVL1_IRQ , 0 , BIT ( 3 ) ) ,
REGMAP_IRQ_REG ( BXTWC_ADC_LVL1_IRQ , 0 , BIT ( 4 ) ) ,
REGMAP_IRQ_REG ( BXTWC_CHGR_LVL1_IRQ , 0 , BIT ( 5 ) ) ,
REGMAP_IRQ_REG ( BXTWC_GPIO_LVL1_IRQ , 0 , BIT ( 6 ) ) ,
REGMAP_IRQ_REG ( BXTWC_CRIT_LVL1_IRQ , 0 , BIT ( 7 ) ) ,
REGMAP_IRQ_REG ( BXTWC_PWRBTN_IRQ , 1 , 0x03 ) ,
} ;
static const struct regmap_irq bxtwc_regmap_irqs_level2 [ ] = {
REGMAP_IRQ_REG ( BXTWC_THRM0_IRQ , 0 , 0xff ) ,
REGMAP_IRQ_REG ( BXTWC_THRM1_IRQ , 1 , 0xbf ) ,
REGMAP_IRQ_REG ( BXTWC_THRM2_IRQ , 2 , 0xff ) ,
REGMAP_IRQ_REG ( BXTWC_BCU_IRQ , 3 , 0x1f ) ,
REGMAP_IRQ_REG ( BXTWC_ADC_IRQ , 4 , 0xff ) ,
2016-10-17 10:32:13 +03:00
REGMAP_IRQ_REG ( BXTWC_USBC_IRQ , 5 , BIT ( 5 ) ) ,
REGMAP_IRQ_REG ( BXTWC_CHGR0_IRQ , 5 , 0x1f ) ,
2015-09-14 19:39:19 +03:00
REGMAP_IRQ_REG ( BXTWC_CHGR1_IRQ , 6 , 0x1f ) ,
REGMAP_IRQ_REG ( BXTWC_GPIO0_IRQ , 7 , 0xff ) ,
REGMAP_IRQ_REG ( BXTWC_GPIO1_IRQ , 8 , 0x3f ) ,
REGMAP_IRQ_REG ( BXTWC_CRIT_IRQ , 9 , 0x03 ) ,
} ;
2016-11-07 23:11:47 +03:00
static const struct regmap_irq bxtwc_regmap_irqs_tmu [ ] = {
REGMAP_IRQ_REG ( BXTWC_TMU_IRQ , 0 , 0x06 ) ,
} ;
2015-09-14 19:39:19 +03:00
static struct regmap_irq_chip bxtwc_regmap_irq_chip = {
. name = " bxtwc_irq_chip " ,
. status_base = BXTWC_IRQLVL1 ,
. mask_base = BXTWC_MIRQLVL1 ,
. irqs = bxtwc_regmap_irqs ,
. num_irqs = ARRAY_SIZE ( bxtwc_regmap_irqs ) ,
. num_regs = 2 ,
} ;
static struct regmap_irq_chip bxtwc_regmap_irq_chip_level2 = {
. name = " bxtwc_irq_chip_level2 " ,
. status_base = BXTWC_THRM0IRQ ,
. mask_base = BXTWC_MTHRM0IRQ ,
. irqs = bxtwc_regmap_irqs_level2 ,
. num_irqs = ARRAY_SIZE ( bxtwc_regmap_irqs_level2 ) ,
. num_regs = 10 ,
} ;
2016-11-07 23:11:47 +03:00
static struct regmap_irq_chip bxtwc_regmap_irq_chip_tmu = {
. name = " bxtwc_irq_chip_tmu " ,
. status_base = BXTWC_TMUIRQ ,
. mask_base = BXTWC_MTMUIRQ ,
. irqs = bxtwc_regmap_irqs_tmu ,
. num_irqs = ARRAY_SIZE ( bxtwc_regmap_irqs_tmu ) ,
. num_regs = 1 ,
} ;
2015-09-14 19:39:19 +03:00
static struct resource gpio_resources [ ] = {
DEFINE_RES_IRQ_NAMED ( BXTWC_GPIO0_IRQ , " GPIO0 " ) ,
DEFINE_RES_IRQ_NAMED ( BXTWC_GPIO1_IRQ , " GPIO1 " ) ,
} ;
static struct resource adc_resources [ ] = {
DEFINE_RES_IRQ_NAMED ( BXTWC_ADC_IRQ , " ADC " ) ,
} ;
2016-07-21 03:33:56 +03:00
static struct resource usbc_resources [ ] = {
2016-10-17 10:32:13 +03:00
DEFINE_RES_IRQ ( BXTWC_USBC_IRQ ) ,
2016-07-21 03:33:56 +03:00
} ;
2015-09-14 19:39:19 +03:00
static struct resource charger_resources [ ] = {
DEFINE_RES_IRQ_NAMED ( BXTWC_CHGR0_IRQ , " CHARGER " ) ,
DEFINE_RES_IRQ_NAMED ( BXTWC_CHGR1_IRQ , " CHARGER1 " ) ,
} ;
static struct resource thermal_resources [ ] = {
DEFINE_RES_IRQ ( BXTWC_THRM0_IRQ ) ,
DEFINE_RES_IRQ ( BXTWC_THRM1_IRQ ) ,
DEFINE_RES_IRQ ( BXTWC_THRM2_IRQ ) ,
} ;
static struct resource bcu_resources [ ] = {
DEFINE_RES_IRQ_NAMED ( BXTWC_BCU_IRQ , " BCU " ) ,
} ;
2016-11-07 23:11:47 +03:00
static struct resource tmu_resources [ ] = {
DEFINE_RES_IRQ_NAMED ( BXTWC_TMU_IRQ , " TMU " ) ,
} ;
2015-09-14 19:39:19 +03:00
static struct mfd_cell bxt_wc_dev [ ] = {
{
. name = " bxt_wcove_gpadc " ,
. num_resources = ARRAY_SIZE ( adc_resources ) ,
. resources = adc_resources ,
} ,
{
. name = " bxt_wcove_thermal " ,
. num_resources = ARRAY_SIZE ( thermal_resources ) ,
. resources = thermal_resources ,
} ,
2016-07-21 03:33:56 +03:00
{
. name = " bxt_wcove_usbc " ,
. num_resources = ARRAY_SIZE ( usbc_resources ) ,
. resources = usbc_resources ,
} ,
2015-09-14 19:39:19 +03:00
{
. name = " bxt_wcove_ext_charger " ,
. num_resources = ARRAY_SIZE ( charger_resources ) ,
. resources = charger_resources ,
} ,
{
. name = " bxt_wcove_bcu " ,
. num_resources = ARRAY_SIZE ( bcu_resources ) ,
. resources = bcu_resources ,
} ,
2016-11-07 23:11:47 +03:00
{
. name = " bxt_wcove_tmu " ,
. num_resources = ARRAY_SIZE ( tmu_resources ) ,
. resources = tmu_resources ,
} ,
2015-09-14 19:39:19 +03:00
{
. name = " bxt_wcove_gpio " ,
. num_resources = ARRAY_SIZE ( gpio_resources ) ,
. resources = gpio_resources ,
} ,
{
. name = " bxt_wcove_region " ,
} ,
} ;
static int regmap_ipc_byte_reg_read ( void * context , unsigned int reg ,
unsigned int * val )
{
int ret ;
int i2c_addr ;
u8 ipc_in [ 2 ] ;
u8 ipc_out [ 4 ] ;
struct intel_soc_pmic * pmic = context ;
if ( reg & REG_ADDR_MASK )
i2c_addr = ( reg & REG_ADDR_MASK ) > > REG_ADDR_SHIFT ;
else {
i2c_addr = BXTWC_DEVICE1_ADDR ;
if ( ! i2c_addr ) {
dev_err ( pmic - > dev , " I2C address not set \n " ) ;
return - EINVAL ;
}
}
reg & = REG_OFFSET_MASK ;
ipc_in [ 0 ] = reg ;
ipc_in [ 1 ] = i2c_addr ;
ret = intel_pmc_ipc_command ( PMC_IPC_PMIC_ACCESS ,
PMC_IPC_PMIC_ACCESS_READ ,
ipc_in , sizeof ( ipc_in ) , ( u32 * ) ipc_out , 1 ) ;
if ( ret ) {
dev_err ( pmic - > dev , " Failed to read from PMIC \n " ) ;
return ret ;
}
* val = ipc_out [ 0 ] ;
return 0 ;
}
static int regmap_ipc_byte_reg_write ( void * context , unsigned int reg ,
unsigned int val )
{
int ret ;
int i2c_addr ;
u8 ipc_in [ 3 ] ;
struct intel_soc_pmic * pmic = context ;
if ( reg & REG_ADDR_MASK )
i2c_addr = ( reg & REG_ADDR_MASK ) > > REG_ADDR_SHIFT ;
else {
i2c_addr = BXTWC_DEVICE1_ADDR ;
if ( ! i2c_addr ) {
dev_err ( pmic - > dev , " I2C address not set \n " ) ;
return - EINVAL ;
}
}
reg & = REG_OFFSET_MASK ;
ipc_in [ 0 ] = reg ;
ipc_in [ 1 ] = i2c_addr ;
ipc_in [ 2 ] = val ;
ret = intel_pmc_ipc_command ( PMC_IPC_PMIC_ACCESS ,
PMC_IPC_PMIC_ACCESS_WRITE ,
ipc_in , sizeof ( ipc_in ) , NULL , 0 ) ;
if ( ret ) {
dev_err ( pmic - > dev , " Failed to write to PMIC \n " ) ;
return ret ;
}
return 0 ;
}
/* sysfs interfaces to r/w PMIC registers, required by initial script */
static unsigned long bxtwc_reg_addr ;
static ssize_t bxtwc_reg_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return sprintf ( buf , " 0x%lx \n " , bxtwc_reg_addr ) ;
}
static ssize_t bxtwc_reg_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
if ( kstrtoul ( buf , 0 , & bxtwc_reg_addr ) ) {
dev_err ( dev , " Invalid register address \n " ) ;
return - EINVAL ;
}
return ( ssize_t ) count ;
}
static ssize_t bxtwc_val_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
int ret ;
unsigned int val ;
struct intel_soc_pmic * pmic = dev_get_drvdata ( dev ) ;
ret = regmap_read ( pmic - > regmap , bxtwc_reg_addr , & val ) ;
if ( ret < 0 ) {
dev_err ( dev , " Failed to read 0x%lx \n " , bxtwc_reg_addr ) ;
return - EIO ;
}
return sprintf ( buf , " 0x%02x \n " , val ) ;
}
static ssize_t bxtwc_val_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t count )
{
int ret ;
unsigned int val ;
struct intel_soc_pmic * pmic = dev_get_drvdata ( dev ) ;
2015-09-28 12:56:36 +03:00
ret = kstrtouint ( buf , 0 , & val ) ;
if ( ret )
return ret ;
2015-09-14 19:39:19 +03:00
ret = regmap_write ( pmic - > regmap , bxtwc_reg_addr , val ) ;
if ( ret ) {
dev_err ( dev , " Failed to write value 0x%02x to address 0x%lx " ,
val , bxtwc_reg_addr ) ;
return - EIO ;
}
return count ;
}
static DEVICE_ATTR ( addr , S_IWUSR | S_IRUSR , bxtwc_reg_show , bxtwc_reg_store ) ;
static DEVICE_ATTR ( val , S_IWUSR | S_IRUSR , bxtwc_val_show , bxtwc_val_store ) ;
static struct attribute * bxtwc_attrs [ ] = {
& dev_attr_addr . attr ,
& dev_attr_val . attr ,
NULL
} ;
static const struct attribute_group bxtwc_group = {
. attrs = bxtwc_attrs ,
} ;
static const struct regmap_config bxtwc_regmap_config = {
. reg_bits = 16 ,
. val_bits = 8 ,
. reg_write = regmap_ipc_byte_reg_write ,
. reg_read = regmap_ipc_byte_reg_read ,
} ;
static int bxtwc_probe ( struct platform_device * pdev )
{
int ret ;
acpi_handle handle ;
acpi_status status ;
unsigned long long hrv ;
struct intel_soc_pmic * pmic ;
handle = ACPI_HANDLE ( & pdev - > dev ) ;
status = acpi_evaluate_integer ( handle , " _HRV " , NULL , & hrv ) ;
if ( ACPI_FAILURE ( status ) ) {
dev_err ( & pdev - > dev , " Failed to get PMIC hardware revision \n " ) ;
return - ENODEV ;
}
if ( hrv ! = BROXTON_PMIC_WC_HRV ) {
dev_err ( & pdev - > dev , " Invalid PMIC hardware revision: %llu \n " ,
hrv ) ;
return - ENODEV ;
}
pmic = devm_kzalloc ( & pdev - > dev , sizeof ( * pmic ) , GFP_KERNEL ) ;
if ( ! pmic )
return - ENOMEM ;
ret = platform_get_irq ( pdev , 0 ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Invalid IRQ \n " ) ;
return ret ;
}
pmic - > irq = ret ;
dev_set_drvdata ( & pdev - > dev , pmic ) ;
pmic - > dev = & pdev - > dev ;
pmic - > regmap = devm_regmap_init ( & pdev - > dev , NULL , pmic ,
& bxtwc_regmap_config ) ;
if ( IS_ERR ( pmic - > regmap ) ) {
ret = PTR_ERR ( pmic - > regmap ) ;
dev_err ( & pdev - > dev , " Failed to initialise regmap: %d \n " , ret ) ;
return ret ;
}
ret = regmap_add_irq_chip ( pmic - > regmap , pmic - > irq ,
IRQF_ONESHOT | IRQF_SHARED ,
0 , & bxtwc_regmap_irq_chip ,
& pmic - > irq_chip_data ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to add IRQ chip \n " ) ;
return ret ;
}
ret = regmap_add_irq_chip ( pmic - > regmap , pmic - > irq ,
IRQF_ONESHOT | IRQF_SHARED ,
0 , & bxtwc_regmap_irq_chip_level2 ,
& pmic - > irq_chip_data_level2 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to add secondary IRQ chip \n " ) ;
goto err_irq_chip_level2 ;
}
2016-11-07 23:11:47 +03:00
ret = regmap_add_irq_chip ( pmic - > regmap , pmic - > irq ,
IRQF_ONESHOT | IRQF_SHARED ,
0 , & bxtwc_regmap_irq_chip_tmu ,
& pmic - > irq_chip_data_tmu ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to add TMU IRQ chip \n " ) ;
goto err_irq_chip_tmu ;
}
2015-09-14 19:39:19 +03:00
ret = mfd_add_devices ( & pdev - > dev , PLATFORM_DEVID_NONE , bxt_wc_dev ,
ARRAY_SIZE ( bxt_wc_dev ) , NULL , 0 ,
NULL ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to add devices \n " ) ;
goto err_mfd ;
}
ret = sysfs_create_group ( & pdev - > dev . kobj , & bxtwc_group ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to create sysfs group %d \n " , ret ) ;
goto err_sysfs ;
}
2016-07-21 03:33:56 +03:00
/*
* There is known hw bug . Upon reset BIT 5 of register
* BXTWC_CHGR_LVL1_IRQ is 0 which is the expected value . However ,
* later it ' s set to 1 ( masked ) automatically by hardware . So we
* have the software workaround here to unmaksed it in order to let
* charger interrutp work .
*/
regmap_update_bits ( pmic - > regmap , BXTWC_MIRQLVL1 ,
BXTWC_MIRQLVL1_MCHGR , 0 ) ;
2015-09-14 19:39:19 +03:00
return 0 ;
err_sysfs :
mfd_remove_devices ( & pdev - > dev ) ;
err_mfd :
2016-11-07 23:11:47 +03:00
regmap_del_irq_chip ( pmic - > irq , pmic - > irq_chip_data_tmu ) ;
err_irq_chip_tmu :
2015-09-14 19:39:19 +03:00
regmap_del_irq_chip ( pmic - > irq , pmic - > irq_chip_data_level2 ) ;
err_irq_chip_level2 :
regmap_del_irq_chip ( pmic - > irq , pmic - > irq_chip_data ) ;
return ret ;
}
static int bxtwc_remove ( struct platform_device * pdev )
{
struct intel_soc_pmic * pmic = dev_get_drvdata ( & pdev - > dev ) ;
sysfs_remove_group ( & pdev - > dev . kobj , & bxtwc_group ) ;
mfd_remove_devices ( & pdev - > dev ) ;
regmap_del_irq_chip ( pmic - > irq , pmic - > irq_chip_data ) ;
regmap_del_irq_chip ( pmic - > irq , pmic - > irq_chip_data_level2 ) ;
2016-11-07 23:11:47 +03:00
regmap_del_irq_chip ( pmic - > irq , pmic - > irq_chip_data_tmu ) ;
2015-09-14 19:39:19 +03:00
return 0 ;
}
static void bxtwc_shutdown ( struct platform_device * pdev )
{
struct intel_soc_pmic * pmic = dev_get_drvdata ( & pdev - > dev ) ;
disable_irq ( pmic - > irq ) ;
}
# ifdef CONFIG_PM_SLEEP
static int bxtwc_suspend ( struct device * dev )
{
struct intel_soc_pmic * pmic = dev_get_drvdata ( dev ) ;
disable_irq ( pmic - > irq ) ;
return 0 ;
}
static int bxtwc_resume ( struct device * dev )
{
struct intel_soc_pmic * pmic = dev_get_drvdata ( dev ) ;
enable_irq ( pmic - > irq ) ;
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( bxtwc_pm_ops , bxtwc_suspend , bxtwc_resume ) ;
static const struct acpi_device_id bxtwc_acpi_ids [ ] = {
{ " INT34D3 " , } ,
{ }
} ;
2016-11-01 18:59:50 +03:00
MODULE_DEVICE_TABLE ( acpi , bxtwc_acpi_ids ) ;
2015-09-14 19:39:19 +03:00
static struct platform_driver bxtwc_driver = {
. probe = bxtwc_probe ,
. remove = bxtwc_remove ,
. shutdown = bxtwc_shutdown ,
. driver = {
. name = " BXTWC PMIC " ,
. pm = & bxtwc_pm_ops ,
. acpi_match_table = ACPI_PTR ( bxtwc_acpi_ids ) ,
} ,
} ;
module_platform_driver ( bxtwc_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Qipeng Zha<qipeng.zha@intel.com> " ) ;