diff mbox series

[v2,1/2] ACPI / PMIC: XPower: optimize I2C-bus accesses

Message ID 20210706160923.20273-1-hdegoede@redhat.com
State Accepted
Commit e38ba404f20c4beb1a5d4547567d2934a5b95843
Headers show
Series [v2,1/2] ACPI / PMIC: XPower: optimize I2C-bus accesses | expand

Commit Message

Hans de Goede July 6, 2021, 4:09 p.m. UTC
The I2C-bus to the XPower AXP288 is shared between the Linux kernel and
the SoCs P-Unit. The P-Unit has a semaphore which the kernel must "lock"
before it may use the bus and while the kernel holds the semaphore the CPU
and GPU power-states must not be changed otherwise the system will freeze.

This is a complex process, which is quite expensive. This is all done by
iosf_mbi_block_punit_i2c_access(). To ensure that no unguarded I2C-bus
accesses happen, iosf_mbi_block_punit_i2c_access() gets called by the
I2C-bus-driver for every I2C transfer. Because this is so expensive it
is allowed to call iosf_mbi_block_punit_i2c_access() in a nested
fashion, so that higher-level code which does multiple I2C-transfers can
call it once for a group of transfers, turning the calls done by the
I2C-bus-driver into no-ops.

Add iosf_mbi_block_punit_i2c_access() calls around groups of register
accesses, so that the P-Unit semaphore only needs to be taken once
for each group of register accesses.

Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v2:
-Do not hold the P-Unit sempahore over the usleep_range() in
 intel_xpower_pmic_get_raw_temp()
---
 drivers/acpi/pmic/intel_pmic_xpower.c | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)

Comments

Andy Shevchenko July 6, 2021, 4:35 p.m. UTC | #1
On Tue, Jul 06, 2021 at 06:09:23PM +0200, Hans de Goede wrote:
> The I2C-bus to the XPower AXP288 is shared between the Linux kernel and
> the SoCs P-Unit. The P-Unit has a semaphore which the kernel must "lock"
> before it may use the bus and while the kernel holds the semaphore the CPU
> and GPU power-states must not be changed otherwise the system will freeze.
> 
> This is a complex process, which is quite expensive. This is all done by
> iosf_mbi_block_punit_i2c_access(). To ensure that no unguarded I2C-bus
> accesses happen, iosf_mbi_block_punit_i2c_access() gets called by the
> I2C-bus-driver for every I2C transfer. Because this is so expensive it
> is allowed to call iosf_mbi_block_punit_i2c_access() in a nested
> fashion, so that higher-level code which does multiple I2C-transfers can
> call it once for a group of transfers, turning the calls done by the
> I2C-bus-driver into no-ops.
> 
> The default exec_mipi_pmic_seq_element implementation from
> drivers/acpi/pmic/intel_pmic.c does a regmap_update_bits() call and
> the involved registers are typically marked as volatile in the regmap,
> so this leads to 2 I2C-bus accesses.
> 
> Add a XPower AXP288 specific implementation of exec_mipi_pmic_seq_element
> which calls iosf_mbi_block_punit_i2c_access() calls before the
> regmap_update_bits() call to avoid having to do the whole expensive
> acquire P-Unit semaphore dance twice.

...

The idea for the further improvement

> +	if (i2c_address != 0x34) {
> +		pr_err("%s: Unexpected i2c-addr: 0x%02x (reg-addr 0x%x value 0x%x mask 0x%x)\n",
> +		       __func__, i2c_address, reg_address, value, mask);
> +		return -ENXIO;
> +	}

We have this in intel_pmic.c. Can we reorganize the code the way we will have
this check solely in the intel_pmic.c?
Hans de Goede July 6, 2021, 6:27 p.m. UTC | #2
Hi,

On 7/6/21 6:35 PM, Andy Shevchenko wrote:
> On Tue, Jul 06, 2021 at 06:09:23PM +0200, Hans de Goede wrote:
>> The I2C-bus to the XPower AXP288 is shared between the Linux kernel and
>> the SoCs P-Unit. The P-Unit has a semaphore which the kernel must "lock"
>> before it may use the bus and while the kernel holds the semaphore the CPU
>> and GPU power-states must not be changed otherwise the system will freeze.
>>
>> This is a complex process, which is quite expensive. This is all done by
>> iosf_mbi_block_punit_i2c_access(). To ensure that no unguarded I2C-bus
>> accesses happen, iosf_mbi_block_punit_i2c_access() gets called by the
>> I2C-bus-driver for every I2C transfer. Because this is so expensive it
>> is allowed to call iosf_mbi_block_punit_i2c_access() in a nested
>> fashion, so that higher-level code which does multiple I2C-transfers can
>> call it once for a group of transfers, turning the calls done by the
>> I2C-bus-driver into no-ops.
>>
>> The default exec_mipi_pmic_seq_element implementation from
>> drivers/acpi/pmic/intel_pmic.c does a regmap_update_bits() call and
>> the involved registers are typically marked as volatile in the regmap,
>> so this leads to 2 I2C-bus accesses.
>>
>> Add a XPower AXP288 specific implementation of exec_mipi_pmic_seq_element
>> which calls iosf_mbi_block_punit_i2c_access() calls before the
>> regmap_update_bits() call to avoid having to do the whole expensive
>> acquire P-Unit semaphore dance twice.
> 
> ...
> 
> The idea for the further improvement
> 
>> +	if (i2c_address != 0x34) {
>> +		pr_err("%s: Unexpected i2c-addr: 0x%02x (reg-addr 0x%x value 0x%x mask 0x%x)\n",
>> +		       __func__, i2c_address, reg_address, value, mask);
>> +		return -ENXIO;
>> +	}
> 
> We have this in intel_pmic.c. Can we reorganize the code the way we will have
> this check solely in the intel_pmic.c?

No, the drivers/acpi/pmic/intel_pmic_chtwc.c implementation accepts multiple
i2c addresses because the whiskey cove has multiple register banks split
over different i2c-addressses.

Regards,

Hans
Andy Shevchenko July 7, 2021, 2:04 p.m. UTC | #3
On Tue, Jul 06, 2021 at 08:27:55PM +0200, Hans de Goede wrote:
> On 7/6/21 6:35 PM, Andy Shevchenko wrote:

> > On Tue, Jul 06, 2021 at 06:09:23PM +0200, Hans de Goede wrote:


...

> > The idea for the further improvement

> > 

> >> +	if (i2c_address != 0x34) {

> >> +		pr_err("%s: Unexpected i2c-addr: 0x%02x (reg-addr 0x%x value 0x%x mask 0x%x)\n",

> >> +		       __func__, i2c_address, reg_address, value, mask);

> >> +		return -ENXIO;

> >> +	}

> > 

> > We have this in intel_pmic.c. Can we reorganize the code the way we will have

> > this check solely in the intel_pmic.c?

> 

> No, the drivers/acpi/pmic/intel_pmic_chtwc.c implementation accepts multiple

> i2c addresses because the whiskey cove has multiple register banks split

> over different i2c-addressses.


I think it's still possible (by modifying the field to be an array of accepted
addresses). Anyway, it's matter outside of this patch series and we have time
to think about it.

-- 
With Best Regards,
Andy Shevchenko
Rafael J. Wysocki July 16, 2021, 5:07 p.m. UTC | #4
On Tue, Jul 6, 2021 at 6:09 PM Hans de Goede <hdegoede@redhat.com> wrote:
>

> The I2C-bus to the XPower AXP288 is shared between the Linux kernel and

> the SoCs P-Unit. The P-Unit has a semaphore which the kernel must "lock"

> before it may use the bus and while the kernel holds the semaphore the CPU

> and GPU power-states must not be changed otherwise the system will freeze.

>

> This is a complex process, which is quite expensive. This is all done by

> iosf_mbi_block_punit_i2c_access(). To ensure that no unguarded I2C-bus

> accesses happen, iosf_mbi_block_punit_i2c_access() gets called by the

> I2C-bus-driver for every I2C transfer. Because this is so expensive it

> is allowed to call iosf_mbi_block_punit_i2c_access() in a nested

> fashion, so that higher-level code which does multiple I2C-transfers can

> call it once for a group of transfers, turning the calls done by the

> I2C-bus-driver into no-ops.

>

> Add iosf_mbi_block_punit_i2c_access() calls around groups of register

> accesses, so that the P-Unit semaphore only needs to be taken once

> for each group of register accesses.

>

> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>

> Signed-off-by: Hans de Goede <hdegoede@redhat.com>

> ---

> Changes in v2:

> -Do not hold the P-Unit sempahore over the usleep_range() in

>  intel_xpower_pmic_get_raw_temp()

> ---

>  drivers/acpi/pmic/intel_pmic_xpower.c | 23 ++++++++++++++++++-----

>  1 file changed, 18 insertions(+), 5 deletions(-)

>

> diff --git a/drivers/acpi/pmic/intel_pmic_xpower.c b/drivers/acpi/pmic/intel_pmic_xpower.c

> index a091d5a8392c..5750c5e7d4c6 100644

> --- a/drivers/acpi/pmic/intel_pmic_xpower.c

> +++ b/drivers/acpi/pmic/intel_pmic_xpower.c

> @@ -178,15 +178,17 @@ static int intel_xpower_pmic_update_power(struct regmap *regmap, int reg,

>  {

>         int data, ret;

>

> -       /* GPIO1 LDO regulator needs special handling */

> -       if (reg == XPOWER_GPI1_CTRL)

> -               return regmap_update_bits(regmap, reg, GPI1_LDO_MASK,

> -                                         on ? GPI1_LDO_ON : GPI1_LDO_OFF);

> -

>         ret = iosf_mbi_block_punit_i2c_access();

>         if (ret)

>                 return ret;

>

> +       /* GPIO1 LDO regulator needs special handling */

> +       if (reg == XPOWER_GPI1_CTRL) {

> +               ret = regmap_update_bits(regmap, reg, GPI1_LDO_MASK,

> +                                        on ? GPI1_LDO_ON : GPI1_LDO_OFF);

> +               goto out;

> +       }

> +

>         if (regmap_read(regmap, reg, &data)) {

>                 ret = -EIO;

>                 goto out;

> @@ -234,6 +236,11 @@ static int intel_xpower_pmic_get_raw_temp(struct regmap *regmap, int reg)

>                 return ret;

>

>         if (adc_ts_pin_ctrl & AXP288_ADC_TS_CURRENT_ON_OFF_MASK) {

> +               /*

> +                * AXP288_ADC_TS_PIN_CTRL reads are cached by the regmap, so

> +                * this does to a single I2C-transfer, and thus there is no

> +                * need to explicitly call iosf_mbi_block_punit_i2c_access().

> +                */

>                 ret = regmap_update_bits(regmap, AXP288_ADC_TS_PIN_CTRL,

>                                          AXP288_ADC_TS_CURRENT_ON_OFF_MASK,

>                                          AXP288_ADC_TS_CURRENT_ON_ONDEMAND);

> @@ -244,6 +251,10 @@ static int intel_xpower_pmic_get_raw_temp(struct regmap *regmap, int reg)

>                 usleep_range(6000, 10000);

>         }

>

> +       ret = iosf_mbi_block_punit_i2c_access();

> +       if (ret)

> +               return ret;

> +

>         ret = regmap_bulk_read(regmap, AXP288_GP_ADC_H, buf, 2);

>         if (ret == 0)

>                 ret = (buf[0] << 4) + ((buf[1] >> 4) & 0x0f);

> @@ -254,6 +265,8 @@ static int intel_xpower_pmic_get_raw_temp(struct regmap *regmap, int reg)

>                                    AXP288_ADC_TS_CURRENT_ON);

>         }

>

> +       iosf_mbi_unblock_punit_i2c_access();

> +

>         return ret;

>  }

>

> --


Applied along with the [2/2] as 5.15 material, thanks!
diff mbox series

Patch

diff --git a/drivers/acpi/pmic/intel_pmic_xpower.c b/drivers/acpi/pmic/intel_pmic_xpower.c
index a091d5a8392c..5750c5e7d4c6 100644
--- a/drivers/acpi/pmic/intel_pmic_xpower.c
+++ b/drivers/acpi/pmic/intel_pmic_xpower.c
@@ -178,15 +178,17 @@  static int intel_xpower_pmic_update_power(struct regmap *regmap, int reg,
 {
 	int data, ret;
 
-	/* GPIO1 LDO regulator needs special handling */
-	if (reg == XPOWER_GPI1_CTRL)
-		return regmap_update_bits(regmap, reg, GPI1_LDO_MASK,
-					  on ? GPI1_LDO_ON : GPI1_LDO_OFF);
-
 	ret = iosf_mbi_block_punit_i2c_access();
 	if (ret)
 		return ret;
 
+	/* GPIO1 LDO regulator needs special handling */
+	if (reg == XPOWER_GPI1_CTRL) {
+		ret = regmap_update_bits(regmap, reg, GPI1_LDO_MASK,
+					 on ? GPI1_LDO_ON : GPI1_LDO_OFF);
+		goto out;
+	}
+
 	if (regmap_read(regmap, reg, &data)) {
 		ret = -EIO;
 		goto out;
@@ -234,6 +236,11 @@  static int intel_xpower_pmic_get_raw_temp(struct regmap *regmap, int reg)
 		return ret;
 
 	if (adc_ts_pin_ctrl & AXP288_ADC_TS_CURRENT_ON_OFF_MASK) {
+		/*
+		 * AXP288_ADC_TS_PIN_CTRL reads are cached by the regmap, so
+		 * this does to a single I2C-transfer, and thus there is no
+		 * need to explicitly call iosf_mbi_block_punit_i2c_access().
+		 */
 		ret = regmap_update_bits(regmap, AXP288_ADC_TS_PIN_CTRL,
 					 AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
 					 AXP288_ADC_TS_CURRENT_ON_ONDEMAND);
@@ -244,6 +251,10 @@  static int intel_xpower_pmic_get_raw_temp(struct regmap *regmap, int reg)
 		usleep_range(6000, 10000);
 	}
 
+	ret = iosf_mbi_block_punit_i2c_access();
+	if (ret)
+		return ret;
+
 	ret = regmap_bulk_read(regmap, AXP288_GP_ADC_H, buf, 2);
 	if (ret == 0)
 		ret = (buf[0] << 4) + ((buf[1] >> 4) & 0x0f);
@@ -254,6 +265,8 @@  static int intel_xpower_pmic_get_raw_temp(struct regmap *regmap, int reg)
 				   AXP288_ADC_TS_CURRENT_ON);
 	}
 
+	iosf_mbi_unblock_punit_i2c_access();
+
 	return ret;
 }