diff mbox series

[v2,1/2] cpufreq: qcom-cpufreq-hw: Clear dcvs interrupts

Message ID 20220401071424.2869057-2-vladimir.zapolskiy@linaro.org
State New
Headers show
Series cpufreq: qcom-cpufreq-hw: Fixes to DCVS interrupt handling on EPSS | expand

Commit Message

Vladimir Zapolskiy April 1, 2022, 7:14 a.m. UTC
It's noted that dcvs interrupts are not self-clearing, thus an interrupt
handler runs constantly, which leads to a severe regression in runtime.
To fix the problem an explicit write to clear interrupt register is
required.

Fixes: 275157b367f4 ("cpufreq: qcom-cpufreq-hw: Add dcvs interrupt support")
Signed-off-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
---
Changes from v1 to v2:
* added a check for pending interrupt status before its handling,
  thanks to Bjorn for review

 drivers/cpufreq/qcom-cpufreq-hw.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

Comments

Bjorn Andersson April 1, 2022, 10:24 p.m. UTC | #1
On Fri 01 Apr 00:14 PDT 2022, Vladimir Zapolskiy wrote:

> It's noted that dcvs interrupts are not self-clearing, thus an interrupt
> handler runs constantly, which leads to a severe regression in runtime.
> To fix the problem an explicit write to clear interrupt register is
> required.
> 
> Fixes: 275157b367f4 ("cpufreq: qcom-cpufreq-hw: Add dcvs interrupt support")
> Signed-off-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
> ---
> Changes from v1 to v2:
> * added a check for pending interrupt status before its handling,
>   thanks to Bjorn for review
> 
>  drivers/cpufreq/qcom-cpufreq-hw.c | 14 ++++++++++++++
>  1 file changed, 14 insertions(+)
> 
> diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c
> index f9d593ff4718..e17413a6f120 100644
> --- a/drivers/cpufreq/qcom-cpufreq-hw.c
> +++ b/drivers/cpufreq/qcom-cpufreq-hw.c
> @@ -24,6 +24,8 @@
>  #define CLK_HW_DIV			2
>  #define LUT_TURBO_IND			1
>  
> +#define GT_IRQ_STATUS			BIT(2)
> +
>  #define HZ_PER_KHZ			1000
>  
>  struct qcom_cpufreq_soc_data {
> @@ -31,6 +33,8 @@ struct qcom_cpufreq_soc_data {
>  	u32 reg_dcvs_ctrl;
>  	u32 reg_freq_lut;
>  	u32 reg_volt_lut;
> +	u32 reg_intr_clr;
> +	u32 reg_intr_status;
>  	u32 reg_current_vote;
>  	u32 reg_perf_state;
>  	u8 lut_row_size;
> @@ -345,11 +349,19 @@ static void qcom_lmh_dcvs_poll(struct work_struct *work)
>  static irqreturn_t qcom_lmh_dcvs_handle_irq(int irq, void *data)
>  {
>  	struct qcom_cpufreq_data *c_data = data;
> +	u32 val;
> +
> +	val = readl_relaxed(c_data->base + c_data->soc_data->reg_intr_status);

Seems reasonable to read the INTR_STATUS register and bail early if
there's no interrupt.

> +	if (!(val & GT_IRQ_STATUS))
> +		return IRQ_HANDLED;

But if we in the interrupt handler realize that there's no interrupt
pending for us, shouldn't we return IRQ_NONE?

>  
>  	/* Disable interrupt and enable polling */
>  	disable_irq_nosync(c_data->throttle_irq);
>  	schedule_delayed_work(&c_data->throttle_work, 0);
>  
> +	writel_relaxed(GT_IRQ_STATUS,
> +		       c_data->base + c_data->soc_data->reg_intr_clr);

And in OSM (i.e. not epss_soc_data), both reg_intr_status and
reg_intr_clr will be 0, so we end up reading and writing the wrong
register.

You need to do:
	if (c_data->soc_data->reg_intr_clr)
		writel_relaxed(..., reg_intr_clr);

But according to the downstream driver, this is supposed to be done in
the polling function, right before you do enable_irq().

Regards,
Bjorn

> +
>  	return IRQ_HANDLED;
>  }
>  
> @@ -368,6 +380,8 @@ static const struct qcom_cpufreq_soc_data epss_soc_data = {
>  	.reg_dcvs_ctrl = 0xb0,
>  	.reg_freq_lut = 0x100,
>  	.reg_volt_lut = 0x200,
> +	.reg_intr_clr = 0x308,
> +	.reg_intr_status = 0x30c,
>  	.reg_perf_state = 0x320,
>  	.lut_row_size = 4,
>  };
> -- 
> 2.33.0
>
Vladimir Zapolskiy April 3, 2022, 7:46 p.m. UTC | #2
Hi Bjorn,

On 4/2/22 01:24, Bjorn Andersson wrote:
> On Fri 01 Apr 00:14 PDT 2022, Vladimir Zapolskiy wrote:
> 
>> It's noted that dcvs interrupts are not self-clearing, thus an interrupt
>> handler runs constantly, which leads to a severe regression in runtime.
>> To fix the problem an explicit write to clear interrupt register is
>> required.
>>
>> Fixes: 275157b367f4 ("cpufreq: qcom-cpufreq-hw: Add dcvs interrupt support")
>> Signed-off-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
>> ---
>> Changes from v1 to v2:
>> * added a check for pending interrupt status before its handling,
>>    thanks to Bjorn for review
>>
>>   drivers/cpufreq/qcom-cpufreq-hw.c | 14 ++++++++++++++
>>   1 file changed, 14 insertions(+)
>>
>> diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c
>> index f9d593ff4718..e17413a6f120 100644
>> --- a/drivers/cpufreq/qcom-cpufreq-hw.c
>> +++ b/drivers/cpufreq/qcom-cpufreq-hw.c
>> @@ -24,6 +24,8 @@
>>   #define CLK_HW_DIV			2
>>   #define LUT_TURBO_IND			1
>>   
>> +#define GT_IRQ_STATUS			BIT(2)
>> +
>>   #define HZ_PER_KHZ			1000
>>   
>>   struct qcom_cpufreq_soc_data {
>> @@ -31,6 +33,8 @@ struct qcom_cpufreq_soc_data {
>>   	u32 reg_dcvs_ctrl;
>>   	u32 reg_freq_lut;
>>   	u32 reg_volt_lut;
>> +	u32 reg_intr_clr;
>> +	u32 reg_intr_status;
>>   	u32 reg_current_vote;
>>   	u32 reg_perf_state;
>>   	u8 lut_row_size;
>> @@ -345,11 +349,19 @@ static void qcom_lmh_dcvs_poll(struct work_struct *work)
>>   static irqreturn_t qcom_lmh_dcvs_handle_irq(int irq, void *data)
>>   {
>>   	struct qcom_cpufreq_data *c_data = data;
>> +	u32 val;
>> +
>> +	val = readl_relaxed(c_data->base + c_data->soc_data->reg_intr_status);
> 
> Seems reasonable to read the INTR_STATUS register and bail early if
> there's no interrupt.
> 
>> +	if (!(val & GT_IRQ_STATUS))
>> +		return IRQ_HANDLED;
> 
> But if we in the interrupt handler realize that there's no interrupt
> pending for us, shouldn't we return IRQ_NONE?
> 

To my knowledge returning IRQ_NONE assumes that right the same interrupt should
be still handled, either by another interrupt handler or by the original handler
again.

I believe here there is no difference in the sense above, since the interrupt is
not shared, it might happen that the check is always void and it should get its
justification, and definitely it's safe to omit the check/return here and just
make another poll/irq enable round, so, as the simplest working fix v1 of the
change without this check should be sufficient.

>>   
>>   	/* Disable interrupt and enable polling */
>>   	disable_irq_nosync(c_data->throttle_irq);
>>   	schedule_delayed_work(&c_data->throttle_work, 0);
>>   
>> +	writel_relaxed(GT_IRQ_STATUS,
>> +		       c_data->base + c_data->soc_data->reg_intr_clr);
> 
> And in OSM (i.e. not epss_soc_data), both reg_intr_status and
> reg_intr_clr will be 0, so we end up reading and writing the wrong
> register.
> 
> You need to do:
> 	if (c_data->soc_data->reg_intr_clr)
> 		writel_relaxed(..., reg_intr_clr);
> 

My understanding is that non-EPSS platforms do not specify a DCVS interrupt
under cpufreq-hw IP, if so, the interrupt handler is not registered and thus
the check for non-zero reg_intr_clr or other interrupt related registers is
not needed, please correct me.

> But according to the downstream driver, this is supposed to be done in
> the polling function, right before you do enable_irq().

The fact about downstream driver is true, however I believe functionally
there is no significant difference between clearing the interrupt status
after disabling the interrupt as above or right before enabling the interrupt
as in OSM.

The code above is simpler and arranged in the most relevant place, to my
understanding is slightly more correct, which is also proven by the testing.

--
Best wishes,
Vladimir

> Regards,
> Bjorn
> 
>> +
>>   	return IRQ_HANDLED;
>>   }
>>   
>> @@ -368,6 +380,8 @@ static const struct qcom_cpufreq_soc_data epss_soc_data = {
>>   	.reg_dcvs_ctrl = 0xb0,
>>   	.reg_freq_lut = 0x100,
>>   	.reg_volt_lut = 0x200,
>> +	.reg_intr_clr = 0x308,
>> +	.reg_intr_status = 0x30c,
>>   	.reg_perf_state = 0x320,
>>   	.lut_row_size = 4,
>>   };
>> -- 
>> 2.33.0
>>
Dmitry Baryshkov April 4, 2022, 12:34 p.m. UTC | #3
On 02/04/2022 01:24, Bjorn Andersson wrote:
> On Fri 01 Apr 00:14 PDT 2022, Vladimir Zapolskiy wrote:
> 
>> It's noted that dcvs interrupts are not self-clearing, thus an interrupt
>> handler runs constantly, which leads to a severe regression in runtime.
>> To fix the problem an explicit write to clear interrupt register is
>> required.
>>
>> Fixes: 275157b367f4 ("cpufreq: qcom-cpufreq-hw: Add dcvs interrupt support")
>> Signed-off-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
>> ---
>> Changes from v1 to v2:
>> * added a check for pending interrupt status before its handling,
>>    thanks to Bjorn for review
>>
>>   drivers/cpufreq/qcom-cpufreq-hw.c | 14 ++++++++++++++
>>   1 file changed, 14 insertions(+)
>>
>> diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c
>> index f9d593ff4718..e17413a6f120 100644
>> --- a/drivers/cpufreq/qcom-cpufreq-hw.c
>> +++ b/drivers/cpufreq/qcom-cpufreq-hw.c
>> @@ -24,6 +24,8 @@
>>   #define CLK_HW_DIV			2
>>   #define LUT_TURBO_IND			1
>>   
>> +#define GT_IRQ_STATUS			BIT(2)
>> +
>>   #define HZ_PER_KHZ			1000
>>   
>>   struct qcom_cpufreq_soc_data {
>> @@ -31,6 +33,8 @@ struct qcom_cpufreq_soc_data {
>>   	u32 reg_dcvs_ctrl;
>>   	u32 reg_freq_lut;
>>   	u32 reg_volt_lut;
>> +	u32 reg_intr_clr;
>> +	u32 reg_intr_status;
>>   	u32 reg_current_vote;
>>   	u32 reg_perf_state;
>>   	u8 lut_row_size;
>> @@ -345,11 +349,19 @@ static void qcom_lmh_dcvs_poll(struct work_struct *work)
>>   static irqreturn_t qcom_lmh_dcvs_handle_irq(int irq, void *data)
>>   {
>>   	struct qcom_cpufreq_data *c_data = data;
>> +	u32 val;
>> +

Following the discussion below (regarding reg_int_clr), the driver 
should also check that soc_data->reg_intr_status is not 0.

>> +	val = readl_relaxed(c_data->base + c_data->soc_data->reg_intr_status);
> 
> Seems reasonable to read the INTR_STATUS register and bail early if
> there's no interrupt.
> 
>> +	if (!(val & GT_IRQ_STATUS))
>> +		return IRQ_HANDLED;
> 
> But if we in the interrupt handler realize that there's no interrupt
> pending for us, shouldn't we return IRQ_NONE?

Initially I wanted to agree with Vladimir. However after giving a 
thought, returning IRQ_HANDLED here can hide other status bits being set 
(e.g. on the other platforms using EPSS). If we return IRQ_NONE here, 
we'll get the "IRQ: nobody cared" message and will know that some bits 
from status are unhandled.

However, a separate thing. We discussed this with Vladimir. I agree with 
him that this chunk is not directly related to the fix for the issue.
I'd suggest to split this patch into two patches:

- writel_relaxed to clear the interrupt (which can be picked up into the 
-rc branch and into stable kernels)

- a check for the GT_IRQ_STATUS which is not strictly necessary, so it 
can come through the plain -next process.

> 
>>   
>>   	/* Disable interrupt and enable polling */
>>   	disable_irq_nosync(c_data->throttle_irq);
>>   	schedule_delayed_work(&c_data->throttle_work, 0);
>>   
>> +	writel_relaxed(GT_IRQ_STATUS,
>> +		       c_data->base + c_data->soc_data->reg_intr_clr);
> 
> And in OSM (i.e. not epss_soc_data), both reg_intr_status and
> reg_intr_clr will be 0, so we end up reading and writing the wrong
> register.
> 
> You need to do:
> 	if (c_data->soc_data->reg_intr_clr)
> 		writel_relaxed(..., reg_intr_clr);

I'd second this. Despite this chunk being called from the path in which 
reg_int_clr is always set, I'd still prefer to have a check. I do not 
like the idea of writing to an optional register without an explicit 
check (or without a comment that this function should be used only when 
reg_int_clr/reg_intr_status are defined).

> 
> But according to the downstream driver, this is supposed to be done in
> the polling function, right before you do enable_irq().
> 
> Regards,
> Bjorn
> 
>> +
>>   	return IRQ_HANDLED;
>>   }
>>   
>> @@ -368,6 +380,8 @@ static const struct qcom_cpufreq_soc_data epss_soc_data = {
>>   	.reg_dcvs_ctrl = 0xb0,
>>   	.reg_freq_lut = 0x100,
>>   	.reg_volt_lut = 0x200,
>> +	.reg_intr_clr = 0x308,
>> +	.reg_intr_status = 0x30c,
>>   	.reg_perf_state = 0x320,
>>   	.lut_row_size = 4,
>>   };
>> -- 
>> 2.33.0
>>
diff mbox series

Patch

diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c
index f9d593ff4718..e17413a6f120 100644
--- a/drivers/cpufreq/qcom-cpufreq-hw.c
+++ b/drivers/cpufreq/qcom-cpufreq-hw.c
@@ -24,6 +24,8 @@ 
 #define CLK_HW_DIV			2
 #define LUT_TURBO_IND			1
 
+#define GT_IRQ_STATUS			BIT(2)
+
 #define HZ_PER_KHZ			1000
 
 struct qcom_cpufreq_soc_data {
@@ -31,6 +33,8 @@  struct qcom_cpufreq_soc_data {
 	u32 reg_dcvs_ctrl;
 	u32 reg_freq_lut;
 	u32 reg_volt_lut;
+	u32 reg_intr_clr;
+	u32 reg_intr_status;
 	u32 reg_current_vote;
 	u32 reg_perf_state;
 	u8 lut_row_size;
@@ -345,11 +349,19 @@  static void qcom_lmh_dcvs_poll(struct work_struct *work)
 static irqreturn_t qcom_lmh_dcvs_handle_irq(int irq, void *data)
 {
 	struct qcom_cpufreq_data *c_data = data;
+	u32 val;
+
+	val = readl_relaxed(c_data->base + c_data->soc_data->reg_intr_status);
+	if (!(val & GT_IRQ_STATUS))
+		return IRQ_HANDLED;
 
 	/* Disable interrupt and enable polling */
 	disable_irq_nosync(c_data->throttle_irq);
 	schedule_delayed_work(&c_data->throttle_work, 0);
 
+	writel_relaxed(GT_IRQ_STATUS,
+		       c_data->base + c_data->soc_data->reg_intr_clr);
+
 	return IRQ_HANDLED;
 }
 
@@ -368,6 +380,8 @@  static const struct qcom_cpufreq_soc_data epss_soc_data = {
 	.reg_dcvs_ctrl = 0xb0,
 	.reg_freq_lut = 0x100,
 	.reg_volt_lut = 0x200,
+	.reg_intr_clr = 0x308,
+	.reg_intr_status = 0x30c,
 	.reg_perf_state = 0x320,
 	.lut_row_size = 4,
 };