2020-05-31 02:15:12 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright ( c ) 2018 - 2020 , The Linux Foundation . All rights reserved .
*/
# include <linux/bitfield.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/irqdomain.h>
# include <linux/mailbox_controller.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <dt-bindings/mailbox/qcom-ipcc.h>
/* IPCC Register offsets */
# define IPCC_REG_SEND_ID 0x0c
# define IPCC_REG_RECV_ID 0x10
# define IPCC_REG_RECV_SIGNAL_ENABLE 0x14
# define IPCC_REG_RECV_SIGNAL_DISABLE 0x18
# define IPCC_REG_RECV_SIGNAL_CLEAR 0x1c
# define IPCC_REG_CLIENT_CLEAR 0x38
# define IPCC_SIGNAL_ID_MASK GENMASK(15, 0)
# define IPCC_CLIENT_ID_MASK GENMASK(31, 16)
# define IPCC_NO_PENDING_IRQ GENMASK(31, 0)
/**
* struct qcom_ipcc_chan_info - Per - mailbox - channel info
* @ client_id : The client - id to which the interrupt has to be triggered
* @ signal_id : The signal - id to which the interrupt has to be triggered
*/
struct qcom_ipcc_chan_info {
u16 client_id ;
u16 signal_id ;
} ;
/**
* struct qcom_ipcc - Holder for the mailbox driver
* @ dev : Device associated with this instance
* @ base : Base address of the IPCC frame associated to APSS
* @ irq_domain : The irq_domain associated with this instance
2022-05-01 13:34:27 +03:00
* @ chans : The mailbox channels array
2020-05-31 02:15:12 +03:00
* @ mchan : The per - mailbox channel info array
* @ mbox : The mailbox controller
2022-05-01 13:34:27 +03:00
* @ num_chans : Number of @ chans elements
2020-05-31 02:15:12 +03:00
* @ irq : Summary irq
*/
struct qcom_ipcc {
struct device * dev ;
void __iomem * base ;
struct irq_domain * irq_domain ;
2021-11-22 08:05:06 +03:00
struct mbox_chan * chans ;
struct qcom_ipcc_chan_info * mchan ;
2020-05-31 02:15:12 +03:00
struct mbox_controller mbox ;
2021-11-22 08:05:06 +03:00
int num_chans ;
2020-05-31 02:15:12 +03:00
int irq ;
} ;
static inline struct qcom_ipcc * to_qcom_ipcc ( struct mbox_controller * mbox )
{
return container_of ( mbox , struct qcom_ipcc , mbox ) ;
}
static inline u32 qcom_ipcc_get_hwirq ( u16 client_id , u16 signal_id )
{
return FIELD_PREP ( IPCC_CLIENT_ID_MASK , client_id ) |
FIELD_PREP ( IPCC_SIGNAL_ID_MASK , signal_id ) ;
}
static irqreturn_t qcom_ipcc_irq_fn ( int irq , void * data )
{
struct qcom_ipcc * ipcc = data ;
u32 hwirq ;
int virq ;
for ( ; ; ) {
hwirq = readl ( ipcc - > base + IPCC_REG_RECV_ID ) ;
if ( hwirq = = IPCC_NO_PENDING_IRQ )
break ;
virq = irq_find_mapping ( ipcc - > irq_domain , hwirq ) ;
writel ( hwirq , ipcc - > base + IPCC_REG_RECV_SIGNAL_CLEAR ) ;
generic_handle_irq ( virq ) ;
}
return IRQ_HANDLED ;
}
static void qcom_ipcc_mask_irq ( struct irq_data * irqd )
{
struct qcom_ipcc * ipcc = irq_data_get_irq_chip_data ( irqd ) ;
irq_hw_number_t hwirq = irqd_to_hwirq ( irqd ) ;
writel ( hwirq , ipcc - > base + IPCC_REG_RECV_SIGNAL_DISABLE ) ;
}
static void qcom_ipcc_unmask_irq ( struct irq_data * irqd )
{
struct qcom_ipcc * ipcc = irq_data_get_irq_chip_data ( irqd ) ;
irq_hw_number_t hwirq = irqd_to_hwirq ( irqd ) ;
writel ( hwirq , ipcc - > base + IPCC_REG_RECV_SIGNAL_ENABLE ) ;
}
static struct irq_chip qcom_ipcc_irq_chip = {
. name = " ipcc " ,
. irq_mask = qcom_ipcc_mask_irq ,
. irq_unmask = qcom_ipcc_unmask_irq ,
. flags = IRQCHIP_SKIP_SET_WAKE ,
} ;
static int qcom_ipcc_domain_map ( struct irq_domain * d , unsigned int irq ,
irq_hw_number_t hw )
{
struct qcom_ipcc * ipcc = d - > host_data ;
irq_set_chip_and_handler ( irq , & qcom_ipcc_irq_chip , handle_level_irq ) ;
irq_set_chip_data ( irq , ipcc ) ;
irq_set_noprobe ( irq ) ;
return 0 ;
}
static int qcom_ipcc_domain_xlate ( struct irq_domain * d ,
struct device_node * node , const u32 * intspec ,
unsigned int intsize ,
unsigned long * out_hwirq ,
unsigned int * out_type )
{
if ( intsize ! = 3 )
return - EINVAL ;
* out_hwirq = qcom_ipcc_get_hwirq ( intspec [ 0 ] , intspec [ 1 ] ) ;
* out_type = intspec [ 2 ] & IRQ_TYPE_SENSE_MASK ;
return 0 ;
}
static const struct irq_domain_ops qcom_ipcc_irq_ops = {
. map = qcom_ipcc_domain_map ,
. xlate = qcom_ipcc_domain_xlate ,
} ;
static int qcom_ipcc_mbox_send_data ( struct mbox_chan * chan , void * data )
{
struct qcom_ipcc * ipcc = to_qcom_ipcc ( chan - > mbox ) ;
struct qcom_ipcc_chan_info * mchan = chan - > con_priv ;
u32 hwirq ;
hwirq = qcom_ipcc_get_hwirq ( mchan - > client_id , mchan - > signal_id ) ;
writel ( hwirq , ipcc - > base + IPCC_REG_SEND_ID ) ;
return 0 ;
}
2021-06-16 20:42:58 +03:00
static void qcom_ipcc_mbox_shutdown ( struct mbox_chan * chan )
{
chan - > con_priv = NULL ;
}
2020-05-31 02:15:12 +03:00
static struct mbox_chan * qcom_ipcc_mbox_xlate ( struct mbox_controller * mbox ,
const struct of_phandle_args * ph )
{
struct qcom_ipcc * ipcc = to_qcom_ipcc ( mbox ) ;
struct qcom_ipcc_chan_info * mchan ;
struct mbox_chan * chan ;
2021-11-22 08:05:06 +03:00
struct device * dev ;
int chan_id ;
dev = ipcc - > dev ;
2020-05-31 02:15:12 +03:00
if ( ph - > args_count ! = 2 )
return ERR_PTR ( - EINVAL ) ;
2021-11-22 08:05:06 +03:00
for ( chan_id = 0 ; chan_id < mbox - > num_chans ; chan_id + + ) {
chan = & ipcc - > chans [ chan_id ] ;
mchan = chan - > con_priv ;
2020-05-31 02:15:12 +03:00
2021-11-22 08:05:06 +03:00
if ( ! mchan )
break ;
else if ( mchan - > client_id = = ph - > args [ 0 ] & &
mchan - > signal_id = = ph - > args [ 1 ] )
return ERR_PTR ( - EBUSY ) ;
2020-05-31 02:15:12 +03:00
}
2021-11-22 08:05:06 +03:00
if ( chan_id > = mbox - > num_chans )
return ERR_PTR ( - EBUSY ) ;
mchan = devm_kzalloc ( dev , sizeof ( * mchan ) , GFP_KERNEL ) ;
if ( ! mchan )
return ERR_PTR ( - ENOMEM ) ;
mchan - > client_id = ph - > args [ 0 ] ;
mchan - > signal_id = ph - > args [ 1 ] ;
chan - > con_priv = mchan ;
return chan ;
2020-05-31 02:15:12 +03:00
}
static const struct mbox_chan_ops ipcc_mbox_chan_ops = {
. send_data = qcom_ipcc_mbox_send_data ,
2021-06-16 20:42:58 +03:00
. shutdown = qcom_ipcc_mbox_shutdown ,
2020-05-31 02:15:12 +03:00
} ;
2021-11-22 08:05:06 +03:00
static int qcom_ipcc_setup_mbox ( struct qcom_ipcc * ipcc ,
struct device_node * controller_dn )
2020-05-31 02:15:12 +03:00
{
2021-11-22 08:05:06 +03:00
struct of_phandle_args curr_ph ;
struct device_node * client_dn ;
2020-05-31 02:15:12 +03:00
struct mbox_controller * mbox ;
struct device * dev = ipcc - > dev ;
2021-11-22 08:05:06 +03:00
int i , j , ret ;
/*
* Find out the number of clients interested in this mailbox
* and create channels accordingly .
*/
ipcc - > num_chans = 0 ;
for_each_node_with_property ( client_dn , " mboxes " ) {
if ( ! of_device_is_available ( client_dn ) )
continue ;
i = of_count_phandle_with_args ( client_dn ,
" mboxes " , " #mbox-cells " ) ;
for ( j = 0 ; j < i ; j + + ) {
ret = of_parse_phandle_with_args ( client_dn , " mboxes " ,
" #mbox-cells " , j , & curr_ph ) ;
of_node_put ( curr_ph . np ) ;
if ( ! ret & & curr_ph . np = = controller_dn ) {
ipcc - > num_chans + + ;
break ;
}
}
}
/* If no clients are found, skip registering as a mbox controller */
if ( ! ipcc - > num_chans )
return 0 ;
ipcc - > chans = devm_kcalloc ( dev , ipcc - > num_chans ,
sizeof ( struct mbox_chan ) , GFP_KERNEL ) ;
if ( ! ipcc - > chans )
return - ENOMEM ;
2020-05-31 02:15:12 +03:00
mbox = & ipcc - > mbox ;
mbox - > dev = dev ;
2021-11-22 08:05:06 +03:00
mbox - > num_chans = ipcc - > num_chans ;
mbox - > chans = ipcc - > chans ;
2020-05-31 02:15:12 +03:00
mbox - > ops = & ipcc_mbox_chan_ops ;
mbox - > of_xlate = qcom_ipcc_mbox_xlate ;
mbox - > txdone_irq = false ;
mbox - > txdone_poll = false ;
return devm_mbox_controller_register ( dev , mbox ) ;
}
2022-05-17 13:43:00 +03:00
static int qcom_ipcc_pm_resume ( struct device * dev )
{
struct qcom_ipcc * ipcc = dev_get_drvdata ( dev ) ;
u32 hwirq ;
int virq ;
hwirq = readl ( ipcc - > base + IPCC_REG_RECV_ID ) ;
if ( hwirq = = IPCC_NO_PENDING_IRQ )
return 0 ;
virq = irq_find_mapping ( ipcc - > irq_domain , hwirq ) ;
dev_dbg ( dev , " virq: %d triggered client-id: %ld; signal-id: %ld \n " , virq ,
FIELD_GET ( IPCC_CLIENT_ID_MASK , hwirq ) , FIELD_GET ( IPCC_SIGNAL_ID_MASK , hwirq ) ) ;
return 0 ;
}
2020-05-31 02:15:12 +03:00
static int qcom_ipcc_probe ( struct platform_device * pdev )
{
struct qcom_ipcc * ipcc ;
2021-11-22 08:05:07 +03:00
static int id ;
char * name ;
2020-05-31 02:15:12 +03:00
int ret ;
ipcc = devm_kzalloc ( & pdev - > dev , sizeof ( * ipcc ) , GFP_KERNEL ) ;
if ( ! ipcc )
return - ENOMEM ;
ipcc - > dev = & pdev - > dev ;
ipcc - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( ipcc - > base ) )
return PTR_ERR ( ipcc - > base ) ;
ipcc - > irq = platform_get_irq ( pdev , 0 ) ;
if ( ipcc - > irq < 0 )
return ipcc - > irq ;
2021-11-22 08:05:07 +03:00
name = devm_kasprintf ( & pdev - > dev , GFP_KERNEL , " ipcc_%d " , id + + ) ;
if ( ! name )
return - ENOMEM ;
2020-05-31 02:15:12 +03:00
ipcc - > irq_domain = irq_domain_add_tree ( pdev - > dev . of_node ,
& qcom_ipcc_irq_ops , ipcc ) ;
if ( ! ipcc - > irq_domain )
return - ENOMEM ;
2021-11-22 08:05:06 +03:00
ret = qcom_ipcc_setup_mbox ( ipcc , pdev - > dev . of_node ) ;
2020-05-31 02:15:12 +03:00
if ( ret )
goto err_mbox ;
ret = devm_request_irq ( & pdev - > dev , ipcc - > irq , qcom_ipcc_irq_fn ,
2022-10-03 20:08:49 +03:00
IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND |
IRQF_NO_THREAD , name , ipcc ) ;
2020-05-31 02:15:12 +03:00
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Failed to register the irq: %d \n " , ret ) ;
2021-11-22 08:05:06 +03:00
goto err_req_irq ;
2020-05-31 02:15:12 +03:00
}
platform_set_drvdata ( pdev , ipcc ) ;
return 0 ;
2021-11-22 08:05:06 +03:00
err_req_irq :
if ( ipcc - > num_chans )
mbox_controller_unregister ( & ipcc - > mbox ) ;
2020-05-31 02:15:12 +03:00
err_mbox :
irq_domain_remove ( ipcc - > irq_domain ) ;
return ret ;
}
static int qcom_ipcc_remove ( struct platform_device * pdev )
{
struct qcom_ipcc * ipcc = platform_get_drvdata ( pdev ) ;
disable_irq_wake ( ipcc - > irq ) ;
irq_domain_remove ( ipcc - > irq_domain ) ;
return 0 ;
}
static const struct of_device_id qcom_ipcc_of_match [ ] = {
{ . compatible = " qcom,ipcc " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , qcom_ipcc_of_match ) ;
2022-05-17 13:43:00 +03:00
static const struct dev_pm_ops qcom_ipcc_dev_pm_ops = {
2022-05-24 01:47:02 +03:00
NOIRQ_SYSTEM_SLEEP_PM_OPS ( NULL , qcom_ipcc_pm_resume )
2022-05-17 13:43:00 +03:00
} ;
2020-05-31 02:15:12 +03:00
static struct platform_driver qcom_ipcc_driver = {
. probe = qcom_ipcc_probe ,
. remove = qcom_ipcc_remove ,
. driver = {
. name = " qcom-ipcc " ,
. of_match_table = qcom_ipcc_of_match ,
2021-07-16 10:49:46 +03:00
. suppress_bind_attrs = true ,
2022-05-17 13:43:00 +03:00
. pm = pm_sleep_ptr ( & qcom_ipcc_dev_pm_ops ) ,
2020-05-31 02:15:12 +03:00
} ,
} ;
static int __init qcom_ipcc_init ( void )
{
return platform_driver_register ( & qcom_ipcc_driver ) ;
}
arch_initcall ( qcom_ipcc_init ) ;
MODULE_AUTHOR ( " Venkata Narendra Kumar Gutta <vnkgutta@codeaurora.org> " ) ;
MODULE_AUTHOR ( " Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> " ) ;
MODULE_DESCRIPTION ( " Qualcomm Technologies, Inc. IPCC driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;