diff mbox series

[v3,3/3] irqchip: Add Qualcomm MPM controller driver

Message ID 20211202122122.23548-4-shawn.guo@linaro.org
State Superseded
Headers show
Series Add Qualcomm MPM irqchip driver support | expand

Commit Message

Shawn Guo Dec. 2, 2021, 12:21 p.m. UTC
Qualcomm SoCs based on the RPM architecture have a MSM Power Manager (MPM)
in always-on domain. In addition to managing resources during sleep, the
hardware also has an interrupt controller that monitors the interrupts
when the system is asleep, wakes up the APSS when one of these interrupts
occur and replays it to GIC after it becomes operational.

It adds an irqchip driver for this interrupt controller, and here are
some notes about it.

- For given SoC, a fixed number of MPM pins are supported, e.g. 96 pins
  on QCM2290.  Each of these MPM pins can be either a MPM_GIC pin or
  a MPM_GPIO pin. The mapping between MPM_GIC pin and GIC interrupt
  is defined by SoC, as well as the mapping between MPM_GPIO pin and
  GPIO number.  The former mapping can be found as the SoC data in this
  MPM driver, while the latter can be found as the msm_gpio_wakeirq_map[]
  in TLMM driver.

- All the register settings are done by APSS on an internal memory
  region called vMPM, and RPM will flush them into hardware after it
  receives a mailbox/IPC notification from APSS.

- When SoC gets awake from sleep mode, the driver will receive an
  interrupt from RPM, so that it can replay interrupt for particular
  polarity.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
 drivers/irqchip/Kconfig    |   8 +
 drivers/irqchip/Makefile   |   1 +
 drivers/irqchip/qcom-mpm.c | 481 +++++++++++++++++++++++++++++++++++++
 3 files changed, 490 insertions(+)
 create mode 100644 drivers/irqchip/qcom-mpm.c

Comments

kernel test robot Dec. 3, 2021, midnight UTC | #1
Hi Shawn,

I love your patch! Yet something to improve:

[auto build test ERROR on tip/irq/core]
[also build test ERROR on linux/master linus/master v5.16-rc3 next-20211202]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Shawn-Guo/Add-Qualcomm-MPM-irqchip-driver-support/20211202-202327
base:   https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git 4946f15e8c334840bf277a0bf924371eae120fcd
config: arm-allmodconfig (https://download.01.org/0day-ci/archive/20211203/202112030740.6LvRpZLQ-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 11.2.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/d575d38e462a2d09c7e36150fb9a638d591a9c91
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Shawn-Guo/Add-Qualcomm-MPM-irqchip-driver-support/20211202-202327
        git checkout d575d38e462a2d09c7e36150fb9a638d591a9c91
        # save the config file to linux build tree
        mkdir build_dir
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross O=build_dir ARCH=arm SHELL=/bin/bash drivers/irqchip/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   drivers/irqchip/qcom-mpm.c: In function 'qcom_mpm_handler':
>> drivers/irqchip/qcom-mpm.c:284:33: error: implicit declaration of function 'irq_set_irqchip_state'; did you mean 'irq_set_chip_data'? [-Werror=implicit-function-declaration]
     284 |                                 irq_set_irqchip_state(d->irq,
         |                                 ^~~~~~~~~~~~~~~~~~~~~
         |                                 irq_set_chip_data
>> drivers/irqchip/qcom-mpm.c:285:49: error: 'IRQCHIP_STATE_PENDING' undeclared (first use in this function)
     285 |                                                 IRQCHIP_STATE_PENDING, true);
         |                                                 ^~~~~~~~~~~~~~~~~~~~~
   drivers/irqchip/qcom-mpm.c:285:49: note: each undeclared identifier is reported only once for each function it appears in
   drivers/irqchip/qcom-mpm.c: In function 'qcom_mpm_init':
>> drivers/irqchip/qcom-mpm.c:425:15: error: implicit declaration of function 'devm_request_irq'; did you mean 'can_request_irq'? [-Werror=implicit-function-declaration]
     425 |         ret = devm_request_irq(dev, irq, qcom_mpm_handler,
         |               ^~~~~~~~~~~~~~~~
         |               can_request_irq
>> drivers/irqchip/qcom-mpm.c:426:32: error: 'IRQF_TRIGGER_RISING' undeclared (first use in this function); did you mean 'IRQD_TRIGGER_MASK'?
     426 |                                IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
         |                                ^~~~~~~~~~~~~~~~~~~
         |                                IRQD_TRIGGER_MASK
>> drivers/irqchip/qcom-mpm.c:426:54: error: 'IRQF_NO_SUSPEND' undeclared (first use in this function)
     426 |                                IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
         |                                                      ^~~~~~~~~~~~~~~
   cc1: some warnings being treated as errors


vim +284 drivers/irqchip/qcom-mpm.c

   264	
   265	/* Triggered by RPM when system resumes from deep sleep */
   266	static irqreturn_t qcom_mpm_handler(int irq, void *dev_id)
   267	{
   268		struct qcom_mpm_priv *priv = dev_id;
   269		unsigned long enable, pending;
   270		int i, j;
   271	
   272		for (i = 0; i < priv->reg_stride; i++) {
   273			enable = qcom_mpm_read(priv, MPM_REG_ENABLE, i);
   274			pending = qcom_mpm_read(priv, MPM_REG_STATUS, i);
   275			pending &= enable;
   276	
   277			for_each_set_bit(j, &pending, 32) {
   278				unsigned int pin = 32 * i + j;
   279				struct irq_desc *desc =
   280						irq_resolve_mapping(priv->domain, pin);
   281				struct irq_data *d = &desc->irq_data;
   282	
   283				if (!irqd_is_level_type(d))
 > 284					irq_set_irqchip_state(d->irq,
 > 285							IRQCHIP_STATE_PENDING, true);
   286	
   287			}
   288		}
   289	
   290		return IRQ_HANDLED;
   291	}
   292	
   293	static int qcom_mpm_enter_sleep(struct qcom_mpm_priv *priv)
   294	{
   295		int i, ret;
   296	
   297		for (i = 0; i < priv->reg_stride; i++)
   298			qcom_mpm_write(priv, MPM_REG_STATUS, i, 0);
   299	
   300		/* Notify RPM to write vMPM into HW */
   301		ret = mbox_send_message(priv->mbox_chan, NULL);
   302		if (ret < 0)
   303			return ret;
   304	
   305		return 0;
   306	}
   307	
   308	static int qcom_mpm_cpu_pm_callback(struct notifier_block *nb,
   309					    unsigned long action, void *data)
   310	{
   311		struct qcom_mpm_priv *priv = container_of(nb, struct qcom_mpm_priv,
   312							  pm_nb);
   313		int ret = NOTIFY_OK;
   314		int cpus_in_pm;
   315	
   316		switch (action) {
   317		case CPU_PM_ENTER:
   318			cpus_in_pm = atomic_inc_return(&priv->cpus_in_pm);
   319			/*
   320			 * NOTE: comments for num_online_cpus() point out that it's
   321			 * only a snapshot so we need to be careful. It should be OK
   322			 * for us to use, though.  It's important for us not to miss
   323			 * if we're the last CPU going down so it would only be a
   324			 * problem if a CPU went offline right after we did the check
   325			 * AND that CPU was not idle AND that CPU was the last non-idle
   326			 * CPU. That can't happen. CPUs would have to come out of idle
   327			 * before the CPU could go offline.
   328			 */
   329			if (cpus_in_pm < num_online_cpus())
   330				return NOTIFY_OK;
   331			break;
   332		case CPU_PM_ENTER_FAILED:
   333		case CPU_PM_EXIT:
   334			atomic_dec(&priv->cpus_in_pm);
   335			return NOTIFY_OK;
   336		default:
   337			return NOTIFY_DONE;
   338		}
   339	
   340		/*
   341		 * It's likely we're on the last CPU. Grab the lock and write MPM for
   342		 * sleep. Grabbing the lock means that if we race with another CPU
   343		 * coming up we are still guaranteed to be safe.
   344		 */
   345		if (raw_spin_trylock(&priv->lock)) {
   346			if (qcom_mpm_enter_sleep(priv))
   347				ret = NOTIFY_BAD;
   348			raw_spin_unlock(&priv->lock);
   349		} else {
   350			/* Another CPU must be up */
   351			return NOTIFY_OK;
   352		}
   353	
   354		if (ret == NOTIFY_BAD) {
   355			/* Double-check if we're here because someone else is up */
   356			if (cpus_in_pm < num_online_cpus())
   357				ret = NOTIFY_OK;
   358			else
   359				/* We won't be called w/ CPU_PM_ENTER_FAILED */
   360				atomic_dec(&priv->cpus_in_pm);
   361		}
   362	
   363		return ret;
   364	}
   365	
   366	static int qcom_mpm_init(struct platform_device *pdev,
   367				 struct device_node *parent,
   368				 const struct mpm_data *data)
   369	{
   370		struct irq_domain *parent_domain;
   371		struct device *dev = &pdev->dev;
   372		struct device_node *np = dev->of_node;
   373		struct qcom_mpm_priv *priv;
   374		unsigned int pin_num;
   375		int irq;
   376		int ret;
   377	
   378		if (!data)
   379			return -ENODEV;
   380	
   381		priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
   382		if (!priv)
   383			return -ENOMEM;
   384	
   385		priv->data = data;
   386		pin_num = priv->data->pin_num;
   387		priv->reg_stride = DIV_ROUND_UP(pin_num, 32);
   388	
   389		raw_spin_lock_init(&priv->lock);
   390	
   391		priv->base = devm_platform_ioremap_resource(pdev, 0);
   392		if (!priv->base)
   393			return PTR_ERR(priv->base);
   394	
   395		irq = platform_get_irq(pdev, 0);
   396		if (irq < 0)
   397			return irq;
   398	
   399		priv->mbox_client.dev = dev;
   400		priv->mbox_chan = mbox_request_channel(&priv->mbox_client, 0);
   401		if (IS_ERR(priv->mbox_chan)) {
   402			ret = PTR_ERR(priv->mbox_chan);
   403			dev_err(dev, "failed to acquire IPC channel: %d\n", ret);
   404			return ret;
   405		}
   406	
   407		parent_domain = irq_find_host(parent);
   408		if (!parent_domain) {
   409			dev_err(dev, "failed to find MPM parent domain\n");
   410			ret = -ENXIO;
   411			goto free_mbox;
   412		}
   413	
   414		priv->domain = irq_domain_create_hierarchy(parent_domain,
   415					IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP, pin_num,
   416					of_node_to_fwnode(np), &qcom_mpm_ops, priv);
   417		if (!priv->domain) {
   418			dev_err(dev, "failed to create MPM domain\n");
   419			ret = -ENOMEM;
   420			goto free_mbox;
   421		}
   422	
   423		irq_domain_update_bus_token(priv->domain, DOMAIN_BUS_WAKEUP);
   424	
 > 425		ret = devm_request_irq(dev, irq, qcom_mpm_handler,
 > 426				       IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
   427				       "qcom_mpm", priv);
   428		if (ret) {
   429			dev_err(dev, "failed to request irq: %d\n", ret);
   430			goto remove_domain;
   431		}
   432	
   433		priv->pm_nb.notifier_call = qcom_mpm_cpu_pm_callback;
   434		cpu_pm_register_notifier(&priv->pm_nb);
   435	
   436		dev_set_drvdata(dev, priv);
   437	
   438		return 0;
   439	
   440	remove_domain:
   441		irq_domain_remove(priv->domain);
   442	free_mbox:
   443		mbox_free_channel(priv->mbox_chan);
   444		return ret;
   445	}
   446	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Shawn Guo Dec. 6, 2021, 1:15 p.m. UTC | #2
On Mon, Dec 06, 2021 at 09:46:29AM +0000, Marc Zyngier wrote:
[...]
> > +/* Triggered by RPM when system resumes from deep sleep */
> > +static irqreturn_t qcom_mpm_handler(int irq, void *dev_id)
> > +{
> > +	struct qcom_mpm_priv *priv = dev_id;
> > +	unsigned long enable, pending;
> > +	int i, j;
> > +
> > +	for (i = 0; i < priv->reg_stride; i++) {
> > +		enable = qcom_mpm_read(priv, MPM_REG_ENABLE, i);
> > +		pending = qcom_mpm_read(priv, MPM_REG_STATUS, i);
> > +		pending &= enable;
> > +
> > +		for_each_set_bit(j, &pending, 32) {
> > +			unsigned int pin = 32 * i + j;
> > +			struct irq_desc *desc =
> > +					irq_resolve_mapping(priv->domain, pin);
> 
> Assignments on a single line, please.

This is to avoid over 80 columns check patch warning.

> 
> > +			struct irq_data *d = &desc->irq_data;
> > +
> > +			if (!irqd_is_level_type(d))
> > +				irq_set_irqchip_state(d->irq,
> > +						IRQCHIP_STATE_PENDING, true);
> > +
> > +		}
> > +	}
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int qcom_mpm_enter_sleep(struct qcom_mpm_priv *priv)
> > +{
> > +	int i, ret;
> > +
> > +	for (i = 0; i < priv->reg_stride; i++)
> > +		qcom_mpm_write(priv, MPM_REG_STATUS, i, 0);
> > +
> > +	/* Notify RPM to write vMPM into HW */
> 
> What do you mean by 'into HW'? We just did that, right? or are these
> registers just fake and most of the stuff is in the RPM?

I have a note about this in commit log.

- All the register settings are done by APSS on an internal memory
  region called vMPM, and RPM will flush them into hardware after it
  receives a mailbox/IPC notification from APSS.

So yes, these registers are fake/virtual in memory, and RPM will
actually flush the values into the MPM hardware block.

> 
> > +	ret = mbox_send_message(priv->mbox_chan, NULL);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static int qcom_mpm_cpu_pm_callback(struct notifier_block *nb,
> > +				    unsigned long action, void *data)
> > +{
> > +	struct qcom_mpm_priv *priv = container_of(nb, struct qcom_mpm_priv,
> > +						  pm_nb);
> > +	int ret = NOTIFY_OK;
> > +	int cpus_in_pm;
> > +
> > +	switch (action) {
> > +	case CPU_PM_ENTER:
> > +		cpus_in_pm = atomic_inc_return(&priv->cpus_in_pm);
> > +		/*
> > +		 * NOTE: comments for num_online_cpus() point out that it's
> > +		 * only a snapshot so we need to be careful. It should be OK
> > +		 * for us to use, though.  It's important for us not to miss
> > +		 * if we're the last CPU going down so it would only be a
> > +		 * problem if a CPU went offline right after we did the check
> > +		 * AND that CPU was not idle AND that CPU was the last non-idle
> > +		 * CPU. That can't happen. CPUs would have to come out of idle
> > +		 * before the CPU could go offline.
> > +		 */
> > +		if (cpus_in_pm < num_online_cpus())
> > +			return NOTIFY_OK;
> > +		break;
> 
> I'm really not keen on this sort of tracking in an irqchip driver.
> Nobody else needs this. This is also copy-pasted (without even
> mentioning it) from rpmh_rsc_cpu_pm_callback(). Why is that logic
> needed twice, given that the RPM is also involved is this sequence?

Yes, this is copy-pasted from rpmh-rsc and I should have mentioned that
in some way.  But rpmh-rsc is a driver specific to RPM-Hardened (RPMH)
which can be found on Qualcomm high-range SoCs like SDM845, SC7180.  And
on these RPMH based SoCs, PDC works as the wake-up interrupt controller.
However, on mid-range SoCs like SDM660, QCM2290, they run a different
combination, that is RPM + MPM.  Note - RPMH and RPM are two similar but
different and separate blocks.

What we are implementing is for RPM + MPM based SoCs, and rpmh-rsc is
irrelevant here.  The same cpu_pm tracking logic can be reused though.

> 
> > +	case CPU_PM_ENTER_FAILED:
> > +	case CPU_PM_EXIT:
> > +		atomic_dec(&priv->cpus_in_pm);
> > +		return NOTIFY_OK;
> > +	default:
> > +		return NOTIFY_DONE;
> > +	}
> > +
> > +	/*
> > +	 * It's likely we're on the last CPU. Grab the lock and write MPM for
> > +	 * sleep. Grabbing the lock means that if we race with another CPU
> > +	 * coming up we are still guaranteed to be safe.
> > +	 */
> > +	if (raw_spin_trylock(&priv->lock)) {
> > +		if (qcom_mpm_enter_sleep(priv))
> > +			ret = NOTIFY_BAD;
> > +		raw_spin_unlock(&priv->lock);
> > +	} else {
> > +		/* Another CPU must be up */
> > +		return NOTIFY_OK;
> > +	}
> > +
> > +	if (ret == NOTIFY_BAD) {
> > +		/* Double-check if we're here because someone else is up */
> > +		if (cpus_in_pm < num_online_cpus())
> > +			ret = NOTIFY_OK;
> > +		else
> > +			/* We won't be called w/ CPU_PM_ENTER_FAILED */
> > +			atomic_dec(&priv->cpus_in_pm);
> > +	}
> > +
> > +	return ret;
> > +}

[...]

> > +/*
> > + * The mapping between MPM_GIC pin and GIC SPI number on QCM2290.  It's taken
> > + * from downstream qcom-mpm-scuba.c with a little transform on the GIC
> > + * SPI numbers (the second column).  Due to the binding difference from
> > + * the downstream, where GIC SPI numbering starts from 32, we expect the
> > + * numbering starts from 0 here, and that's why we have the number minus 32
> > + * comparing to the downstream.
> 
> This doesn't answer my earlier question: is this list complete? Or is
> it likely to change?

Yes, it's complete for QCM2290.

> Also, why is that in not in the device tree,
> given that it is likely to change from one SoC to another?

Yes, this is a SoC specific data, and will be different from one SoC to
another.  Different subsystem maintainers have different views on such
SoC data.  Some think they belong to kernel/driver and can be matched
using SoC specific compatible, while others prefer to have them in DT.

As I mentioned in the commit log, this SoC data actually consists of two
parts:

 1) Map of MPM_GIC pin <--> GIC interrupt number
 2) Map of MPM_GPIO pin <--> GPIO number

Since we already have map 2) defined in pinctrl driver rather than DT,
I put map 1) in MPM driver.

Shawn

> 
> > + */
> > +const struct mpm_pin qcm2290_gic_pins[] = {
> > +	{ 2, 275 },	/* tsens0_tsens_upper_lower_int */
> > +	{ 5, 296 },	/* lpass_irq_out_sdc */
> > +	{ 12, 422 },	/* b3_lfps_rxterm_irq */
> > +	{ 24, 79 },	/* bi_px_lpi_1_aoss_mx */
> > +	{ 86, 183 },	/* mpm_wake,spmi_m */
> > +	{ 90, 260 },	/* eud_p0_dpse_int_mx */
> > +	{ 91, 260 },	/* eud_p0_dmse_int_mx */
> > +	{ -1 },
> > +};
> > +
> > +const struct mpm_data qcm2290_data = {
> > +	.pin_num = 96,
> > +	.gic_pins = qcm2290_gic_pins,
> > +};
> > +
> > +static int qcm2290_mpm_init(struct platform_device *pdev,
> > +			    struct device_node *parent)
> > +{
> > +	return qcom_mpm_init(pdev, parent, &qcm2290_data);
> > +}
> > +
> > +IRQCHIP_PLATFORM_DRIVER_BEGIN(qcom_mpm)
> > +IRQCHIP_MATCH("qcom,qcm2290-mpm", qcm2290_mpm_init)
> > +IRQCHIP_PLATFORM_DRIVER_END(qcom_mpm)
> > +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. MSM Power Manager");
> > +MODULE_LICENSE("GPL v2");
Shawn Guo Dec. 8, 2021, 10:04 a.m. UTC | #3
On Tue, Dec 07, 2021 at 10:16:32AM +0000, Marc Zyngier wrote:
> On Tue, 07 Dec 2021 09:48:36 +0000,
> Shawn Guo <shawn.guo@linaro.org> wrote:
> > 
> > On Mon, Dec 06, 2021 at 01:48:12PM +0000, Marc Zyngier wrote:
> > > > > > +static int qcom_mpm_enter_sleep(struct qcom_mpm_priv *priv)
> > > > > > +{
> > > > > > +	int i, ret;
> > > > > > +
> > > > > > +	for (i = 0; i < priv->reg_stride; i++)
> > > > > > +		qcom_mpm_write(priv, MPM_REG_STATUS, i, 0);
> > > > > > +
> > > > > > +	/* Notify RPM to write vMPM into HW */
> > > > > 
> > > > > What do you mean by 'into HW'? We just did that, right? or are these
> > > > > registers just fake and most of the stuff is in the RPM?
> > > > 
> > > > I have a note about this in commit log.
> > > > 
> > > > - All the register settings are done by APSS on an internal memory
> > > >   region called vMPM, and RPM will flush them into hardware after it
> > > >   receives a mailbox/IPC notification from APSS.
> > > > 
> > > > So yes, these registers are fake/virtual in memory, and RPM will
> > > > actually flush the values into the MPM hardware block.
> > > 
> > > Then why are you using MMIO accessors all over the place if this is
> > > just RAM? Who *owns* this memory? Is it normal DRAM? Or some flops
> > > exposed by a device? Why isn't the state simply communicated over the
> > > mailbox instead?
> > 
> > It's a piece of internal memory (SRAM) which can be access by AP and
> > RPM.  The communication mechanism is defined by SoC/RPM design, and we
> > can do nothing but following the procedure.
> 
> Then the procedure needs to be documented:

Maulik, I'm trying my best to answer Marc's questions based on my
limited knowledges about the hardware.  Please clarify if there is
anything incorrect.

> - Who owns the memory at any given time?

The memory is owned by APSS when system is awake, and owned by RPM when
APSS gets power collapsed.  RPM is on the always-on domain and will be
managing resources and monitoring wake-up interrupts during sleep, with
the help of MPM.  MPM is not a core/master but a hardware
controller/block.

> - What are the events that trigger a change of ownership?

When APSS is about to get power collapsed, it sends a mailbox
notification to RPM, and RPM will take the ownership.  And when APSS is
woken up by a MPM pin/interrupt, APSS takes back the ownership.

> - What are the messages exchanged between these entities?

The messages exchanged are documented as "vMPM register layout" in the
beginning of the driver.  Basically, they are values of MPM registers
related to wake-up interrupt configurations, which will be directly
dumped into physical MPM registers by RPM, when RPM gets the ownership.
On wake-up, RPM will copy STATUS registers into vMPM memory and return
the ownership back to APSS.

> - What is the synchronisation mechanism between the various processing
>   entities (MPM. RPM, APSS...)?

A hardware IPC/mailbox channel is being used by APSS to tell RPM to take
over the ownership.  On wake-up, the wake-up event itself would be the
synchronisation.

> - Is the per-interrupt tracking a HW requirement or a SW
>   implementation choice? Could this instead be a one-shot operation
>   iterating over the whole state?

I do not quite sure what "per-interrupt tracking" means.  The HW
requirement is just that, when RPM takes the ownership of vMPM memory
region, the memory contains the MPM register values, that RPM can
directly dump into MPM registers.

> All this needs to be explained so that it is maintainable, because I'm
> getting tired of drivers that mimic the QC downstream code without
> justification nor documentation to support the implementation.

I really appreciate all your review comments and questions which are
driving for good and maintainable code!

Shawn
diff mbox series

Patch

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 7038957f4a77..680d2fcf2686 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -430,6 +430,14 @@  config QCOM_PDC
 	  Power Domain Controller driver to manage and configure wakeup
 	  IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
 
+config QCOM_MPM
+	tristate "QCOM MPM"
+	depends on ARCH_QCOM
+	select IRQ_DOMAIN_HIERARCHY
+	help
+	  MSM Power Manager driver to manage and configure wakeup
+	  IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
+
 config CSKY_MPINTC
 	bool
 	depends on CSKY
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index c1f611cbfbf8..0e2e10467e28 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -94,6 +94,7 @@  obj-$(CONFIG_MESON_IRQ_GPIO)		+= irq-meson-gpio.o
 obj-$(CONFIG_GOLDFISH_PIC) 		+= irq-goldfish-pic.o
 obj-$(CONFIG_NDS32)			+= irq-ativic32.o
 obj-$(CONFIG_QCOM_PDC)			+= qcom-pdc.o
+obj-$(CONFIG_QCOM_MPM)			+= qcom-mpm.o
 obj-$(CONFIG_CSKY_MPINTC)		+= irq-csky-mpintc.o
 obj-$(CONFIG_CSKY_APB_INTC)		+= irq-csky-apb-intc.o
 obj-$(CONFIG_RISCV_INTC)		+= irq-riscv-intc.o
diff --git a/drivers/irqchip/qcom-mpm.c b/drivers/irqchip/qcom-mpm.c
new file mode 100644
index 000000000000..4a0dac2e2c6e
--- /dev/null
+++ b/drivers/irqchip/qcom-mpm.c
@@ -0,0 +1,481 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, Linaro Limited
+ * Copyright (c) 2010-2020, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/atomic.h>
+#include <linux/cpu_pm.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/arm-gic-v3.h>
+#include <linux/irqdomain.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/irq.h>
+#include <linux/spinlock.h>
+
+/*
+ * vMPM register layout:
+ *
+ *    31                              0
+ *    +--------------------------------+
+ *    |            TIMER0              | 0x00
+ *    +--------------------------------+
+ *    |            TIMER1              | 0x04
+ *    +--------------------------------+
+ *    |            ENABLE0             | 0x08
+ *    +--------------------------------+
+ *    |              ...               | ...
+ *    +--------------------------------+
+ *    |            ENABLEn             |
+ *    +--------------------------------+
+ *    |          FALLING_EDGE0         |
+ *    +--------------------------------+
+ *    |              ...               |
+ *    +--------------------------------+
+ *    |            STATUSn             |
+ *    +--------------------------------+
+ *
+ * n = DIV_ROUND_UP(pin_num, 32)
+ *
+ */
+#define MPM_REG_ENABLE		0
+#define MPM_REG_FALLING_EDGE	1
+#define MPM_REG_RISING_EDGE	2
+#define MPM_REG_POLARITY	3
+#define MPM_REG_STATUS		4
+
+#define MPM_NO_PARENT_IRQ	~0UL
+
+/* MPM pin and its GIC hwirq */
+struct mpm_pin {
+	int pin;
+	irq_hw_number_t hwirq;
+};
+
+struct mpm_data {
+	unsigned int pin_num;
+	const struct mpm_pin *gic_pins;
+};
+
+struct qcom_mpm_priv {
+	void __iomem *base;
+	raw_spinlock_t lock;
+	struct mbox_client mbox_client;
+	struct mbox_chan *mbox_chan;
+	const struct mpm_data *data;
+	unsigned int reg_stride;
+	struct irq_domain *domain;
+	struct notifier_block pm_nb;
+	atomic_t cpus_in_pm;
+};
+
+static inline u32
+qcom_mpm_read(struct qcom_mpm_priv *priv, unsigned int reg, unsigned int index)
+{
+	unsigned int offset = (reg * priv->reg_stride + index + 2) * 4;
+
+	return readl_relaxed(priv->base + offset);
+}
+
+static inline void
+qcom_mpm_write(struct qcom_mpm_priv *priv, unsigned int reg,
+	       unsigned int index, u32 val)
+{
+	unsigned int offset = (reg * priv->reg_stride + index + 2) * 4;
+
+	writel_relaxed(val, priv->base + offset);
+
+	/* Ensure the write is completed */
+	wmb();
+}
+
+static inline void qcom_mpm_enable_irq(struct irq_data *d, bool en)
+{
+	struct qcom_mpm_priv *priv = d->chip_data;
+	int pin = d->hwirq;
+	unsigned int index = pin / 32;
+	unsigned int shift = pin % 32;
+	u32 val;
+
+	raw_spin_lock(&priv->lock);
+
+	val = qcom_mpm_read(priv, MPM_REG_ENABLE, index);
+	if (en)
+		val |= BIT(shift);
+	else
+		val &= ~BIT(shift);
+	qcom_mpm_write(priv, MPM_REG_ENABLE, index, val);
+
+	raw_spin_unlock(&priv->lock);
+}
+
+static void qcom_mpm_mask(struct irq_data *d)
+{
+	qcom_mpm_enable_irq(d, false);
+
+	if (d->parent_data)
+		irq_chip_mask_parent(d);
+}
+
+static void qcom_mpm_unmask(struct irq_data *d)
+{
+	qcom_mpm_enable_irq(d, true);
+
+	if (d->parent_data)
+		irq_chip_unmask_parent(d);
+}
+
+static inline void
+mpm_set_type(struct qcom_mpm_priv *priv, bool set, unsigned int reg,
+	     unsigned int index, unsigned int shift)
+{
+	u32 val;
+
+	raw_spin_lock(&priv->lock);
+
+	val = qcom_mpm_read(priv, reg, index);
+	if (set)
+		val |= BIT(shift);
+	else
+		val &= ~BIT(shift);
+	qcom_mpm_write(priv, reg, index, val);
+
+	raw_spin_unlock(&priv->lock);
+}
+
+static int qcom_mpm_set_type(struct irq_data *d, unsigned int type)
+{
+	struct qcom_mpm_priv *priv = d->chip_data;
+	int pin = d->hwirq;
+	unsigned int index = pin / 32;
+	unsigned int shift = pin % 32;
+
+	switch (type & IRQ_TYPE_SENSE_MASK) {
+	case IRQ_TYPE_EDGE_RISING:
+		mpm_set_type(priv, !!(type & IRQ_TYPE_EDGE_RISING),
+			     MPM_REG_RISING_EDGE, index, shift);
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		mpm_set_type(priv, !!(type & IRQ_TYPE_EDGE_FALLING),
+			     MPM_REG_FALLING_EDGE, index, shift);
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+		mpm_set_type(priv, !!(type & IRQ_TYPE_LEVEL_HIGH),
+			     MPM_REG_POLARITY, index, shift);
+		break;
+	}
+
+	if (!d->parent_data)
+		return 0;
+
+	if (type & IRQ_TYPE_EDGE_BOTH)
+		type = IRQ_TYPE_EDGE_RISING;
+
+	if (type & IRQ_TYPE_LEVEL_MASK)
+		type = IRQ_TYPE_LEVEL_HIGH;
+
+	return irq_chip_set_type_parent(d, type);
+}
+
+static struct irq_chip qcom_mpm_chip = {
+	.name			= "mpm",
+	.irq_eoi		= irq_chip_eoi_parent,
+	.irq_mask		= qcom_mpm_mask,
+	.irq_unmask		= qcom_mpm_unmask,
+	.irq_retrigger		= irq_chip_retrigger_hierarchy,
+	.irq_set_type		= qcom_mpm_set_type,
+	.irq_set_affinity	= irq_chip_set_affinity_parent,
+	.flags			= IRQCHIP_MASK_ON_SUSPEND |
+				  IRQCHIP_SKIP_SET_WAKE,
+};
+
+static irq_hw_number_t get_parent_hwirq(struct qcom_mpm_priv *priv, int pin)
+{
+	const struct mpm_pin *gic_pins = priv->data->gic_pins;
+	int i;
+
+	for (i = 0; gic_pins[i].pin >= 0; i++) {
+		int p = gic_pins[i].pin;
+
+		if (p < 0)
+			break;
+
+		if (p == pin)
+			return gic_pins[i].hwirq;
+	}
+
+	return MPM_NO_PARENT_IRQ;
+}
+
+static int qcom_mpm_alloc(struct irq_domain *domain, unsigned int virq,
+			  unsigned int nr_irqs, void *data)
+{
+	struct qcom_mpm_priv *priv = domain->host_data;
+	struct irq_fwspec *fwspec = data;
+	struct irq_fwspec parent_fwspec;
+	irq_hw_number_t parent_hwirq;
+	irq_hw_number_t hwirq;
+	unsigned int type;
+	int  ret;
+
+	ret = irq_domain_translate_twocell(domain, fwspec, &hwirq, &type);
+	if (ret)
+		return ret;
+
+	ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+					    &qcom_mpm_chip, priv);
+	if (ret)
+		return ret;
+
+	parent_hwirq = get_parent_hwirq(priv, hwirq);
+	if (parent_hwirq == MPM_NO_PARENT_IRQ)
+		return irq_domain_disconnect_hierarchy(domain->parent, virq);
+
+	if (type & IRQ_TYPE_EDGE_BOTH)
+		type = IRQ_TYPE_EDGE_RISING;
+
+	if (type & IRQ_TYPE_LEVEL_MASK)
+		type = IRQ_TYPE_LEVEL_HIGH;
+
+	parent_fwspec.fwnode = domain->parent->fwnode;
+	parent_fwspec.param_count = 3;
+	parent_fwspec.param[0] = 0;
+	parent_fwspec.param[1] = parent_hwirq;
+	parent_fwspec.param[2] = type;
+
+	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+					    &parent_fwspec);
+}
+
+static const struct irq_domain_ops qcom_mpm_ops = {
+	.alloc		= qcom_mpm_alloc,
+	.free		= irq_domain_free_irqs_common,
+	.translate	= irq_domain_translate_twocell,
+};
+
+/* Triggered by RPM when system resumes from deep sleep */
+static irqreturn_t qcom_mpm_handler(int irq, void *dev_id)
+{
+	struct qcom_mpm_priv *priv = dev_id;
+	unsigned long enable, pending;
+	int i, j;
+
+	for (i = 0; i < priv->reg_stride; i++) {
+		enable = qcom_mpm_read(priv, MPM_REG_ENABLE, i);
+		pending = qcom_mpm_read(priv, MPM_REG_STATUS, i);
+		pending &= enable;
+
+		for_each_set_bit(j, &pending, 32) {
+			unsigned int pin = 32 * i + j;
+			struct irq_desc *desc =
+					irq_resolve_mapping(priv->domain, pin);
+			struct irq_data *d = &desc->irq_data;
+
+			if (!irqd_is_level_type(d))
+				irq_set_irqchip_state(d->irq,
+						IRQCHIP_STATE_PENDING, true);
+
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int qcom_mpm_enter_sleep(struct qcom_mpm_priv *priv)
+{
+	int i, ret;
+
+	for (i = 0; i < priv->reg_stride; i++)
+		qcom_mpm_write(priv, MPM_REG_STATUS, i, 0);
+
+	/* Notify RPM to write vMPM into HW */
+	ret = mbox_send_message(priv->mbox_chan, NULL);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int qcom_mpm_cpu_pm_callback(struct notifier_block *nb,
+				    unsigned long action, void *data)
+{
+	struct qcom_mpm_priv *priv = container_of(nb, struct qcom_mpm_priv,
+						  pm_nb);
+	int ret = NOTIFY_OK;
+	int cpus_in_pm;
+
+	switch (action) {
+	case CPU_PM_ENTER:
+		cpus_in_pm = atomic_inc_return(&priv->cpus_in_pm);
+		/*
+		 * NOTE: comments for num_online_cpus() point out that it's
+		 * only a snapshot so we need to be careful. It should be OK
+		 * for us to use, though.  It's important for us not to miss
+		 * if we're the last CPU going down so it would only be a
+		 * problem if a CPU went offline right after we did the check
+		 * AND that CPU was not idle AND that CPU was the last non-idle
+		 * CPU. That can't happen. CPUs would have to come out of idle
+		 * before the CPU could go offline.
+		 */
+		if (cpus_in_pm < num_online_cpus())
+			return NOTIFY_OK;
+		break;
+	case CPU_PM_ENTER_FAILED:
+	case CPU_PM_EXIT:
+		atomic_dec(&priv->cpus_in_pm);
+		return NOTIFY_OK;
+	default:
+		return NOTIFY_DONE;
+	}
+
+	/*
+	 * It's likely we're on the last CPU. Grab the lock and write MPM for
+	 * sleep. Grabbing the lock means that if we race with another CPU
+	 * coming up we are still guaranteed to be safe.
+	 */
+	if (raw_spin_trylock(&priv->lock)) {
+		if (qcom_mpm_enter_sleep(priv))
+			ret = NOTIFY_BAD;
+		raw_spin_unlock(&priv->lock);
+	} else {
+		/* Another CPU must be up */
+		return NOTIFY_OK;
+	}
+
+	if (ret == NOTIFY_BAD) {
+		/* Double-check if we're here because someone else is up */
+		if (cpus_in_pm < num_online_cpus())
+			ret = NOTIFY_OK;
+		else
+			/* We won't be called w/ CPU_PM_ENTER_FAILED */
+			atomic_dec(&priv->cpus_in_pm);
+	}
+
+	return ret;
+}
+
+static int qcom_mpm_init(struct platform_device *pdev,
+			 struct device_node *parent,
+			 const struct mpm_data *data)
+{
+	struct irq_domain *parent_domain;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct qcom_mpm_priv *priv;
+	unsigned int pin_num;
+	int irq;
+	int ret;
+
+	if (!data)
+		return -ENODEV;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->data = data;
+	pin_num = priv->data->pin_num;
+	priv->reg_stride = DIV_ROUND_UP(pin_num, 32);
+
+	raw_spin_lock_init(&priv->lock);
+
+	priv->base = devm_platform_ioremap_resource(pdev, 0);
+	if (!priv->base)
+		return PTR_ERR(priv->base);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	priv->mbox_client.dev = dev;
+	priv->mbox_chan = mbox_request_channel(&priv->mbox_client, 0);
+	if (IS_ERR(priv->mbox_chan)) {
+		ret = PTR_ERR(priv->mbox_chan);
+		dev_err(dev, "failed to acquire IPC channel: %d\n", ret);
+		return ret;
+	}
+
+	parent_domain = irq_find_host(parent);
+	if (!parent_domain) {
+		dev_err(dev, "failed to find MPM parent domain\n");
+		ret = -ENXIO;
+		goto free_mbox;
+	}
+
+	priv->domain = irq_domain_create_hierarchy(parent_domain,
+				IRQ_DOMAIN_FLAG_QCOM_MPM_WAKEUP, pin_num,
+				of_node_to_fwnode(np), &qcom_mpm_ops, priv);
+	if (!priv->domain) {
+		dev_err(dev, "failed to create MPM domain\n");
+		ret = -ENOMEM;
+		goto free_mbox;
+	}
+
+	irq_domain_update_bus_token(priv->domain, DOMAIN_BUS_WAKEUP);
+
+	ret = devm_request_irq(dev, irq, qcom_mpm_handler,
+			       IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
+			       "qcom_mpm", priv);
+	if (ret) {
+		dev_err(dev, "failed to request irq: %d\n", ret);
+		goto remove_domain;
+	}
+
+	priv->pm_nb.notifier_call = qcom_mpm_cpu_pm_callback;
+	cpu_pm_register_notifier(&priv->pm_nb);
+
+	dev_set_drvdata(dev, priv);
+
+	return 0;
+
+remove_domain:
+	irq_domain_remove(priv->domain);
+free_mbox:
+	mbox_free_channel(priv->mbox_chan);
+	return ret;
+}
+
+/*
+ * The mapping between MPM_GIC pin and GIC SPI number on QCM2290.  It's taken
+ * from downstream qcom-mpm-scuba.c with a little transform on the GIC
+ * SPI numbers (the second column).  Due to the binding difference from
+ * the downstream, where GIC SPI numbering starts from 32, we expect the
+ * numbering starts from 0 here, and that's why we have the number minus 32
+ * comparing to the downstream.
+ */
+const struct mpm_pin qcm2290_gic_pins[] = {
+	{ 2, 275 },	/* tsens0_tsens_upper_lower_int */
+	{ 5, 296 },	/* lpass_irq_out_sdc */
+	{ 12, 422 },	/* b3_lfps_rxterm_irq */
+	{ 24, 79 },	/* bi_px_lpi_1_aoss_mx */
+	{ 86, 183 },	/* mpm_wake,spmi_m */
+	{ 90, 260 },	/* eud_p0_dpse_int_mx */
+	{ 91, 260 },	/* eud_p0_dmse_int_mx */
+	{ -1 },
+};
+
+const struct mpm_data qcm2290_data = {
+	.pin_num = 96,
+	.gic_pins = qcm2290_gic_pins,
+};
+
+static int qcm2290_mpm_init(struct platform_device *pdev,
+			    struct device_node *parent)
+{
+	return qcom_mpm_init(pdev, parent, &qcm2290_data);
+}
+
+IRQCHIP_PLATFORM_DRIVER_BEGIN(qcom_mpm)
+IRQCHIP_MATCH("qcom,qcm2290-mpm", qcm2290_mpm_init)
+IRQCHIP_PLATFORM_DRIVER_END(qcom_mpm)
+MODULE_DESCRIPTION("Qualcomm Technologies, Inc. MSM Power Manager");
+MODULE_LICENSE("GPL v2");