Verdin 8MP PWM Suspend behaviour

I’m attempting to modify the default PWM behaviour for an imx8mp module.

Background: There is a fan on PWM1 which is driven by low side switching and functions when configured as such in the device tree. The PWM is inverted, so a value of 0 corresponds to “full on”. There is a 100K pull-up on the pin.

However, it gets interesting when the module is placed into sleep mode (systemctl suspend). The fan will come on full blast (which appears to be expected since the system seems to pull the pin down while sleeping rather than tri-stating). And on resume, the PWM restores with a duty cycle of 50% (which is unexpected).

I’ve been reading and found that it appears to be possible to define pin configs for suspend by adding an additional pinctrl node with the name “sleep”, but I have not managed to get this to work - I’ve tried several config values (pull up, no pull up, weak pull up), but the pin always goes low when entering suspend - so I’m probably doing something wrong.

&pwm1 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&pinctrl_pwm_1>;
	pinctrl-1 = <&pinctrl_pwm_1_standby>;
	#pwm-cells = <3>;
};
.....
	pinctrl_pwm_1_standby: pwm1grp_sb {
		fsl,pins = <
			MX8MP_IOMUXC_SPDIF_EXT_CLK__PWM1_OUT		0x146	/* SODIMM 15 */
		>; // set PE_ENABLE and PULL_UP bits
	};

Note I’ve also tried changing the pad definition to GPIO instead of PWM in supend without any success either.

The “50% on resume” seems to be either a bug or quirk in the implementation of the pwm-fan.c driver we are using to integrate this fan into the system’s thermal control, as a “normal” PWM channel that is unbound does not appear to exhibit this behaviour.

I also found notes on having PWM “active” during suspend that suggest changing the oscillator and/or ensuring that it is active when sleeping. Can you refer me to some more detailed information on how that’s done? I found the clock specifications in the dts already, but am unsure what one would change them to or how to ensure a selected clock is active in sleep.

in your “sleep” pinctrl node, you can configure the pin as an output and set its state to high or low:

pinctrl_pwm_1_standby: pwm1grp_sb {
fsl,pins = <
MX8MP_IOMUXC_SPDIF_EXT_CLK__GPIO5_IO14 0x146 /* SODIMM 15 */

;
fsl,direction = <1>; // set direction to output
fsl,output-high; // set state to high
};

Can you modify your fan related circuitry to make it logic be non-inverting?

Thanks for the quick response!

Unfortunately that change did not have the desired effect, the PWM pin still drops to 0v when I issue systemctl suspend. I found a note in the PWM driver as follows which suggests this might be a PWM driver limitation:

 * Limitations:
 * - When disabled the output is driven to 0 independent of the configured
 *   polarity.

I have indeed suggested making it non-inverting as a possible future board revision, but regardless of whether we address that in hardware or software, I’m also still interested in whether the PWM can be set to some fixed value while sleeping - There’s a possibility that some components will generate enough heat to require a small degree of cooling (but significantly less than the fan’s maximum airflow/power.
If it can be set to e.g a duty cycle of 10 or 20% that would address both concerns at the same time.

Hi @bw908,

I couldn’t find any reference to the pinctrl “sleep” that you mentioned on the device tree. Checking the Refernece Manual fro the iMX8M Plus, it appears to be possible, since the sleep mode apparently doesn’t disable the PWM peripheral, although is not clear how to do that.

Can you please share you software version? Is it BSP 6? Kernel 5.15?

Best Regards,
Hiago.

Hello Hiago,

We are using TorizonCore 5.7.0/ Kernel 5.4.
The initial reference to these pinctrl states I found was here (which I realize is not Torizon documentation)
https://www.kernel.org/doc/Documentation/devicetree/bindings/pwm/nvidia%2Ctegra20-pwm.txt

but at the time I also recall finding notes indicating that generally speaking, “sleep” was one of the allowed pinctrl state names for this purpose at the kernel level, rather than e.g. something driver specific, which led me to think that it would he handled by the kernel.

Hopefully that provides some clarification

Hi @bw908,

Thanks for the information. Yes, the document you shared is about Tegra 20, which is an old module and doesn’t support TorizonCore.

Ok, I will take a look to check if I find something. I will let you know when I have any updates.

Best Regards,
Hiago.

Hi @bw908,

After some research, I have some thoughts on this issue. From what I could see, there is no mention of sleep function and callbacks on the PWM IMX driver from NXP. Also, there is no mention of sleep on the IMX PWM documentation.

For example, the driver for the STM32 mentions a function from the linux kernel that enabled sleep support:

static int __maybe_unused stm32_pwm_suspend(struct device *dev)
{
	struct stm32_pwm *priv = dev_get_drvdata(dev);
	unsigned int i;
	u32 ccer, mask;

	/* Look for active channels */
	ccer = active_channels(priv);

	for (i = 0; i < priv->chip.npwm; i++) {
		mask = TIM_CCER_CC1E << (i * 4);
		if (ccer & mask) {
			dev_err(dev, "PWM %u still in use by consumer %s\n",
				i, priv->chip.pwms[i].label);
			return -EBUSY;
		}
	}

	return pinctrl_pm_select_sleep_state(dev);
}

static int __maybe_unused stm32_pwm_resume(struct device *dev)
{
	struct stm32_pwm *priv = dev_get_drvdata(dev);
	int ret;

	ret = pinctrl_pm_select_default_state(dev);
	if (ret)
		return ret;

	/* restore breakinput registers that may have been lost in low power */
	return stm32_pwm_apply_breakinputs(priv);
}

by searching for drivers inside the PWM folder that uses this function called “pinctrl_pm_select_sleep_state”, we can see that it doesn’t find anything for IMX:

→ rg pinctrl_pm_select_sleep_state
pwm-tegra.c
314:    return pinctrl_pm_select_sleep_state(dev);

pwm-stm32-lp.c
233:    return pinctrl_pm_select_sleep_state(dev);

pwm-stm32.c
678:    return pinctrl_pm_select_sleep_state(dev);

As you previously mentioned, we can also see that the Tegra board supports this feature.

That being said, apparently, the iMX doesn’t support that. This function “pinctrl_pm_select_sleep_state” probably will search for the sleep state defined under the device tree, so it must be necessary for the sleep state.

If you still want to try and test something, I recommend you post a question on the NXP community, so they can better guide you through their drivers.

Best Regards,
Hiago.

1 Like

Thanks for the information, we’ll look into it more as time permits!

1 Like

I’m happy to report that adding the PM functions (using stm32-pwm-lp as a reference) to the imx driver works just fine, now the fan stays off in sleep mode (or rather, it respects the “sleep” definition in the device-tree). It still comes out of suspend at 40%, but I suspect that is a bug in the pwm-fan driver.

Presumably with further modifications specific to our application it might be possible to set an actual PWM value during sleep.

static int __maybe_unused pwm_imx27_suspend(struct device *dev)
{
	struct pwm_imx27_chip *imx = dev_get_drvdata(dev);
	struct pwm_state state;

	pwm_get_state(&imx->chip.pwms[0], &state);
	if (state.enabled) {
		dev_err(dev, "The consumer didn't stop us (%s)\n",
			imx->chip.pwms[0].label);
		return -EBUSY;
	}

	return pinctrl_pm_select_sleep_state(dev);
}

static int __maybe_unused pwm_imx27_resume(struct device *dev)
{
	return pinctrl_pm_select_default_state(dev);
}

static SIMPLE_DEV_PM_OPS(pwm_imx27_pm_ops, pwm_imx27_suspend,
			 pwm_imx27_resume);

static struct platform_driver imx_pwm_driver = {
	.driver = {
		.name = "pwm-imx27-pm",
		.of_match_table = pwm_imx27_dt_ids,
		.pm = &pwm_imx27_pm_ops,
	},
	.probe = pwm_imx27_probe,
	.remove = pwm_imx27_remove,
};