2011-04-06 01:40:52 +04:00
/*
* Copyright ( c ) 2011 , Code Aurora Forum . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation .
*
* 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 .
*/
# define pr_fmt(fmt) "%s: " fmt, __func__
# include <linux/kernel.h>
2014-02-26 22:59:19 +04:00
# include <linux/interrupt.h>
2014-02-26 22:59:20 +04:00
# include <linux/irqchip/chained_irq.h>
2014-02-26 22:59:19 +04:00
# include <linux/irq.h>
2014-02-26 22:59:21 +04:00
# include <linux/irqdomain.h>
2013-08-20 11:01:26 +04:00
# include <linux/module.h>
2011-04-06 01:40:52 +04:00
# include <linux/platform_device.h>
# include <linux/slab.h>
2011-04-06 01:40:53 +04:00
# include <linux/err.h>
2013-03-12 22:41:54 +04:00
# include <linux/ssbi.h>
2014-02-26 22:59:23 +04:00
# include <linux/regmap.h>
2014-02-26 22:59:21 +04:00
# include <linux/of_platform.h>
2011-04-06 01:40:52 +04:00
# include <linux/mfd/core.h>
2014-02-26 22:59:19 +04:00
# define SSBI_REG_ADDR_IRQ_BASE 0x1BB
# define SSBI_REG_ADDR_IRQ_ROOT (SSBI_REG_ADDR_IRQ_BASE + 0)
# define SSBI_REG_ADDR_IRQ_M_STATUS1 (SSBI_REG_ADDR_IRQ_BASE + 1)
# define SSBI_REG_ADDR_IRQ_M_STATUS2 (SSBI_REG_ADDR_IRQ_BASE + 2)
# define SSBI_REG_ADDR_IRQ_M_STATUS3 (SSBI_REG_ADDR_IRQ_BASE + 3)
# define SSBI_REG_ADDR_IRQ_M_STATUS4 (SSBI_REG_ADDR_IRQ_BASE + 4)
# define SSBI_REG_ADDR_IRQ_BLK_SEL (SSBI_REG_ADDR_IRQ_BASE + 5)
# define SSBI_REG_ADDR_IRQ_IT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 6)
# define SSBI_REG_ADDR_IRQ_CONFIG (SSBI_REG_ADDR_IRQ_BASE + 7)
# define SSBI_REG_ADDR_IRQ_RT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 8)
# define PM_IRQF_LVL_SEL 0x01 /* level select */
# define PM_IRQF_MASK_FE 0x02 /* mask falling edge */
# define PM_IRQF_MASK_RE 0x04 /* mask rising edge */
# define PM_IRQF_CLR 0x08 /* clear interrupt */
# define PM_IRQF_BITS_MASK 0x70
# define PM_IRQF_BITS_SHIFT 4
# define PM_IRQF_WRITE 0x80
# define PM_IRQF_MASK_ALL (PM_IRQF_MASK_FE | \
PM_IRQF_MASK_RE )
2011-04-06 01:40:52 +04:00
# define REG_HWREV 0x002 /* PMIC4 revision */
# define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */
2014-02-26 22:59:21 +04:00
# define PM8921_NR_IRQS 256
2014-02-26 22:59:19 +04:00
struct pm_irq_chip {
2014-02-26 22:59:23 +04:00
struct regmap * regmap ;
2014-02-26 22:59:19 +04:00
spinlock_t pm_irq_lock ;
2014-02-26 22:59:21 +04:00
struct irq_domain * irqdomain ;
2014-02-26 22:59:19 +04:00
unsigned int num_irqs ;
unsigned int num_blocks ;
unsigned int num_masters ;
u8 config [ 0 ] ;
} ;
2014-02-26 22:59:23 +04:00
static int pm8xxx_read_block_irq ( struct pm_irq_chip * chip , unsigned int bp ,
unsigned int * ip )
2014-02-26 22:59:19 +04:00
{
int rc ;
spin_lock ( & chip - > pm_irq_lock ) ;
2014-02-26 22:59:23 +04:00
rc = regmap_write ( chip - > regmap , SSBI_REG_ADDR_IRQ_BLK_SEL , bp ) ;
2014-02-26 22:59:19 +04:00
if ( rc ) {
pr_err ( " Failed Selecting Block %d rc=%d \n " , bp , rc ) ;
goto bail ;
}
2014-02-26 22:59:23 +04:00
rc = regmap_read ( chip - > regmap , SSBI_REG_ADDR_IRQ_IT_STATUS , ip ) ;
2014-02-26 22:59:19 +04:00
if ( rc )
pr_err ( " Failed Reading Status rc=%d \n " , rc ) ;
bail :
spin_unlock ( & chip - > pm_irq_lock ) ;
return rc ;
}
2014-02-26 22:59:23 +04:00
static int
pm8xxx_config_irq ( struct pm_irq_chip * chip , unsigned int bp , unsigned int cp )
2014-02-26 22:59:19 +04:00
{
int rc ;
spin_lock ( & chip - > pm_irq_lock ) ;
2014-02-26 22:59:23 +04:00
rc = regmap_write ( chip - > regmap , SSBI_REG_ADDR_IRQ_BLK_SEL , bp ) ;
2014-02-26 22:59:19 +04:00
if ( rc ) {
pr_err ( " Failed Selecting Block %d rc=%d \n " , bp , rc ) ;
goto bail ;
}
cp | = PM_IRQF_WRITE ;
2014-02-26 22:59:23 +04:00
rc = regmap_write ( chip - > regmap , SSBI_REG_ADDR_IRQ_CONFIG , cp ) ;
2014-02-26 22:59:19 +04:00
if ( rc )
pr_err ( " Failed Configuring IRQ rc=%d \n " , rc ) ;
bail :
spin_unlock ( & chip - > pm_irq_lock ) ;
return rc ;
}
static int pm8xxx_irq_block_handler ( struct pm_irq_chip * chip , int block )
{
int pmirq , irq , i , ret = 0 ;
2014-02-26 22:59:23 +04:00
unsigned int bits ;
2014-02-26 22:59:19 +04:00
ret = pm8xxx_read_block_irq ( chip , block , & bits ) ;
if ( ret ) {
pr_err ( " Failed reading %d block ret=%d " , block , ret ) ;
return ret ;
}
if ( ! bits ) {
pr_err ( " block bit set in master but no irqs: %d " , block ) ;
return 0 ;
}
/* Check IRQ bits */
for ( i = 0 ; i < 8 ; i + + ) {
if ( bits & ( 1 < < i ) ) {
pmirq = block * 8 + i ;
2014-02-26 22:59:21 +04:00
irq = irq_find_mapping ( chip - > irqdomain , pmirq ) ;
2014-02-26 22:59:19 +04:00
generic_handle_irq ( irq ) ;
}
}
return 0 ;
}
static int pm8xxx_irq_master_handler ( struct pm_irq_chip * chip , int master )
{
2014-02-26 22:59:23 +04:00
unsigned int blockbits ;
2014-02-26 22:59:19 +04:00
int block_number , i , ret = 0 ;
2014-02-26 22:59:23 +04:00
ret = regmap_read ( chip - > regmap , SSBI_REG_ADDR_IRQ_M_STATUS1 + master ,
& blockbits ) ;
2014-02-26 22:59:19 +04:00
if ( ret ) {
pr_err ( " Failed to read master %d ret=%d \n " , master , ret ) ;
return ret ;
}
if ( ! blockbits ) {
pr_err ( " master bit set in root but no blocks: %d " , master ) ;
return 0 ;
}
for ( i = 0 ; i < 8 ; i + + )
if ( blockbits & ( 1 < < i ) ) {
block_number = master * 8 + i ; /* block # */
ret | = pm8xxx_irq_block_handler ( chip , block_number ) ;
}
return ret ;
}
static void pm8xxx_irq_handler ( unsigned int irq , struct irq_desc * desc )
{
struct pm_irq_chip * chip = irq_desc_get_handler_data ( desc ) ;
struct irq_chip * irq_chip = irq_desc_get_chip ( desc ) ;
2014-02-26 22:59:23 +04:00
unsigned int root ;
2014-02-26 22:59:19 +04:00
int i , ret , masters = 0 ;
2014-02-26 22:59:20 +04:00
chained_irq_enter ( irq_chip , desc ) ;
2014-02-26 22:59:23 +04:00
ret = regmap_read ( chip - > regmap , SSBI_REG_ADDR_IRQ_ROOT , & root ) ;
2014-02-26 22:59:19 +04:00
if ( ret ) {
pr_err ( " Can't read root status ret=%d \n " , ret ) ;
return ;
}
/* on pm8xxx series masters start from bit 1 of the root */
masters = root > > 1 ;
/* Read allowed masters for blocks. */
for ( i = 0 ; i < chip - > num_masters ; i + + )
if ( masters & ( 1 < < i ) )
pm8xxx_irq_master_handler ( chip , i ) ;
2014-02-26 22:59:20 +04:00
chained_irq_exit ( irq_chip , desc ) ;
2014-02-26 22:59:19 +04:00
}
static void pm8xxx_irq_mask_ack ( struct irq_data * d )
{
struct pm_irq_chip * chip = irq_data_get_irq_chip_data ( d ) ;
2014-02-26 22:59:21 +04:00
unsigned int pmirq = irqd_to_hwirq ( d ) ;
2014-02-26 22:59:19 +04:00
u8 block , config ;
block = pmirq / 8 ;
config = chip - > config [ pmirq ] | PM_IRQF_MASK_ALL | PM_IRQF_CLR ;
pm8xxx_config_irq ( chip , block , config ) ;
}
static void pm8xxx_irq_unmask ( struct irq_data * d )
{
struct pm_irq_chip * chip = irq_data_get_irq_chip_data ( d ) ;
2014-02-26 22:59:21 +04:00
unsigned int pmirq = irqd_to_hwirq ( d ) ;
2014-02-26 22:59:19 +04:00
u8 block , config ;
block = pmirq / 8 ;
config = chip - > config [ pmirq ] ;
pm8xxx_config_irq ( chip , block , config ) ;
}
static int pm8xxx_irq_set_type ( struct irq_data * d , unsigned int flow_type )
{
struct pm_irq_chip * chip = irq_data_get_irq_chip_data ( d ) ;
2014-02-26 22:59:21 +04:00
unsigned int pmirq = irqd_to_hwirq ( d ) ;
int irq_bit ;
2014-02-26 22:59:19 +04:00
u8 block , config ;
block = pmirq / 8 ;
irq_bit = pmirq % 8 ;
chip - > config [ pmirq ] = ( irq_bit < < PM_IRQF_BITS_SHIFT )
| PM_IRQF_MASK_ALL ;
if ( flow_type & ( IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING ) ) {
if ( flow_type & IRQF_TRIGGER_RISING )
chip - > config [ pmirq ] & = ~ PM_IRQF_MASK_RE ;
if ( flow_type & IRQF_TRIGGER_FALLING )
chip - > config [ pmirq ] & = ~ PM_IRQF_MASK_FE ;
} else {
chip - > config [ pmirq ] | = PM_IRQF_LVL_SEL ;
if ( flow_type & IRQF_TRIGGER_HIGH )
chip - > config [ pmirq ] & = ~ PM_IRQF_MASK_RE ;
else
chip - > config [ pmirq ] & = ~ PM_IRQF_MASK_FE ;
}
config = chip - > config [ pmirq ] | PM_IRQF_CLR ;
return pm8xxx_config_irq ( chip , block , config ) ;
}
static struct irq_chip pm8xxx_irq_chip = {
. name = " pm8xxx " ,
. irq_mask_ack = pm8xxx_irq_mask_ack ,
. irq_unmask = pm8xxx_irq_unmask ,
. irq_set_type = pm8xxx_irq_set_type ,
2014-03-04 22:48:52 +04:00
. flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE ,
2014-02-26 22:59:19 +04:00
} ;
2014-02-26 22:59:21 +04:00
static int pm8xxx_irq_domain_map ( struct irq_domain * d , unsigned int irq ,
irq_hw_number_t hwirq )
{
struct pm_irq_chip * chip = d - > host_data ;
2014-02-26 22:59:19 +04:00
2014-02-26 22:59:21 +04:00
irq_set_chip_and_handler ( irq , & pm8xxx_irq_chip , handle_level_irq ) ;
irq_set_chip_data ( irq , chip ) ;
2014-02-26 22:59:19 +04:00
# ifdef CONFIG_ARM
2014-02-26 22:59:21 +04:00
set_irq_flags ( irq , IRQF_VALID ) ;
2014-02-26 22:59:19 +04:00
# else
2014-02-26 22:59:21 +04:00
irq_set_noprobe ( irq ) ;
2014-02-26 22:59:19 +04:00
# endif
return 0 ;
}
2014-02-26 22:59:21 +04:00
static const struct irq_domain_ops pm8xxx_irq_domain_ops = {
. xlate = irq_domain_xlate_twocell ,
. map = pm8xxx_irq_domain_map ,
} ;
2014-02-26 22:59:23 +04:00
static const struct regmap_config ssbi_regmap_config = {
. reg_bits = 16 ,
. val_bits = 8 ,
. max_register = 0x3ff ,
. fast_io = true ,
. reg_read = ssbi_reg_read ,
. reg_write = ssbi_reg_write
} ;
2014-02-26 22:59:24 +04:00
static const struct of_device_id pm8921_id_table [ ] = {
{ . compatible = " qcom,pm8058 " , } ,
{ . compatible = " qcom,pm8921 " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , pm8921_id_table ) ;
2012-11-19 22:23:04 +04:00
static int pm8921_probe ( struct platform_device * pdev )
2011-04-06 01:40:52 +04:00
{
2014-02-26 22:59:23 +04:00
struct regmap * regmap ;
2014-03-05 23:34:25 +04:00
int irq , rc ;
2014-02-26 22:59:23 +04:00
unsigned int val ;
2011-04-06 01:40:53 +04:00
u32 rev ;
2014-02-26 22:59:21 +04:00
struct pm_irq_chip * chip ;
unsigned int nirqs = PM8921_NR_IRQS ;
2011-04-06 01:40:52 +04:00
2014-02-26 22:59:21 +04:00
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 )
return irq ;
2011-04-06 01:40:52 +04:00
2014-02-26 22:59:23 +04:00
regmap = devm_regmap_init ( & pdev - > dev , NULL , pdev - > dev . parent ,
& ssbi_regmap_config ) ;
if ( IS_ERR ( regmap ) )
return PTR_ERR ( regmap ) ;
2011-04-06 01:40:52 +04:00
/* Read PMIC chip revision */
2014-02-26 22:59:23 +04:00
rc = regmap_read ( regmap , REG_HWREV , & val ) ;
2011-04-06 01:40:52 +04:00
if ( rc ) {
pr_err ( " Failed to read hw rev reg %d:rc=%d \n " , REG_HWREV , rc ) ;
2013-08-20 11:02:26 +04:00
return rc ;
2011-04-06 01:40:52 +04:00
}
pr_info ( " PMIC revision 1: %02X \n " , val ) ;
2011-04-06 01:40:53 +04:00
rev = val ;
2011-04-06 01:40:52 +04:00
/* Read PMIC chip revision 2 */
2014-02-26 22:59:23 +04:00
rc = regmap_read ( regmap , REG_HWREV_2 , & val ) ;
2011-04-06 01:40:52 +04:00
if ( rc ) {
pr_err ( " Failed to read hw rev 2 reg %d:rc=%d \n " ,
REG_HWREV_2 , rc ) ;
2013-08-20 11:02:26 +04:00
return rc ;
2011-04-06 01:40:52 +04:00
}
pr_info ( " PMIC revision 2: %02X \n " , val ) ;
2011-04-06 01:40:53 +04:00
rev | = val < < BITS_PER_BYTE ;
2011-04-06 01:40:52 +04:00
2014-02-26 22:59:21 +04:00
chip = devm_kzalloc ( & pdev - > dev , sizeof ( * chip ) +
sizeof ( chip - > config [ 0 ] ) * nirqs ,
GFP_KERNEL ) ;
if ( ! chip )
return - ENOMEM ;
2014-04-09 04:14:15 +04:00
platform_set_drvdata ( pdev , chip ) ;
2014-02-26 22:59:23 +04:00
chip - > regmap = regmap ;
2014-02-26 22:59:21 +04:00
chip - > num_irqs = nirqs ;
chip - > num_blocks = DIV_ROUND_UP ( chip - > num_irqs , 8 ) ;
chip - > num_masters = DIV_ROUND_UP ( chip - > num_blocks , 8 ) ;
spin_lock_init ( & chip - > pm_irq_lock ) ;
chip - > irqdomain = irq_domain_add_linear ( pdev - > dev . of_node , nirqs ,
& pm8xxx_irq_domain_ops ,
chip ) ;
if ( ! chip - > irqdomain )
return - ENODEV ;
irq_set_handler_data ( irq , chip ) ;
irq_set_chained_handler ( irq , pm8xxx_irq_handler ) ;
irq_set_irq_wake ( irq , 1 ) ;
rc = of_platform_populate ( pdev - > dev . of_node , NULL , NULL , & pdev - > dev ) ;
2011-04-06 01:40:53 +04:00
if ( rc ) {
2014-02-26 22:59:21 +04:00
irq_set_chained_handler ( irq , NULL ) ;
irq_set_handler_data ( irq , NULL ) ;
irq_domain_remove ( chip - > irqdomain ) ;
2011-04-06 01:40:53 +04:00
}
2014-02-26 22:59:21 +04:00
return rc ;
}
2011-04-06 01:40:53 +04:00
2014-02-26 22:59:21 +04:00
static int pm8921_remove_child ( struct device * dev , void * unused )
{
platform_device_unregister ( to_platform_device ( dev ) ) ;
2011-04-06 01:40:52 +04:00
return 0 ;
}
2012-11-19 22:26:01 +04:00
static int pm8921_remove ( struct platform_device * pdev )
2011-04-06 01:40:52 +04:00
{
2014-02-26 22:59:21 +04:00
int irq = platform_get_irq ( pdev , 0 ) ;
2014-04-09 04:14:15 +04:00
struct pm_irq_chip * chip = platform_get_drvdata ( pdev ) ;
2014-02-26 22:59:21 +04:00
device_for_each_child ( & pdev - > dev , NULL , pm8921_remove_child ) ;
irq_set_chained_handler ( irq , NULL ) ;
irq_set_handler_data ( irq , NULL ) ;
irq_domain_remove ( chip - > irqdomain ) ;
2011-04-06 01:40:52 +04:00
return 0 ;
}
static struct platform_driver pm8921_driver = {
. probe = pm8921_probe ,
2012-11-19 22:20:24 +04:00
. remove = pm8921_remove ,
2011-04-06 01:40:52 +04:00
. driver = {
. name = " pm8921-core " ,
. owner = THIS_MODULE ,
2014-02-26 22:59:24 +04:00
. of_match_table = pm8921_id_table ,
2011-04-06 01:40:52 +04:00
} ,
} ;
static int __init pm8921_init ( void )
{
return platform_driver_register ( & pm8921_driver ) ;
}
subsys_initcall ( pm8921_init ) ;
static void __exit pm8921_exit ( void )
{
platform_driver_unregister ( & pm8921_driver ) ;
}
module_exit ( pm8921_exit ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " PMIC 8921 core driver " ) ;
MODULE_VERSION ( " 1.0 " ) ;
MODULE_ALIAS ( " platform:pm8921-core " ) ;