Re: [PATCH] soc/tegra: pmc: Fix dual edge triggered wakes

From: Thierry Reding
Date: Wed Sep 14 2022 - 11:13:31 EST


On Tue, Sep 06, 2022 at 01:44:17PM +0000, Petlozu Pravareshwar wrote:
> When a wake event is defined to be triggered on both positive
> and negative edge of the input wake signal, it is crucial to
> know the current state of the signal when going into suspend.
> The intended way to obtain the current state of the wake
> signals is to read the WAKE_AOWAKE_SW_STATUS register,
> which should contains the raw state of the wake signals.
>
> However, this register is edge triggered, an edge will not
> be generated for signals that are already asserted prior to
> the assertion of WAKE_LATCH_SW.
>
> To workaround this, change the polarity of the wake level
> from '0' to '1' while latching the signals, as this will
> generate an edge for signals that are set to '1'.
>
> Signed-off-by: Stefan Kristiansson <stefank@xxxxxxxxxx>
> Signed-off-by: Petlozu Pravareshwar <petlozup@xxxxxxxxxx>
> ---
> drivers/soc/tegra/pmc.c | 141 ++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 136 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
> index 495d16a4732c..6a86961477e8 100644
> --- a/drivers/soc/tegra/pmc.c
> +++ b/drivers/soc/tegra/pmc.c
> @@ -182,6 +182,9 @@
> #define WAKE_AOWAKE_TIER0_ROUTING(x) (0x4b4 + ((x) << 2))
> #define WAKE_AOWAKE_TIER1_ROUTING(x) (0x4c0 + ((x) << 2))
> #define WAKE_AOWAKE_TIER2_ROUTING(x) (0x4cc + ((x) << 2))
> +#define WAKE_AOWAKE_SW_STATUS_W_0 0x49c
> +#define WAKE_AOWAKE_SW_STATUS(x) (0x4a0 + ((x) << 2))
> +#define WAKE_LATCH_SW 0x498
>
> #define WAKE_AOWAKE_CTRL 0x4f4
> #define WAKE_AOWAKE_CTRL_INTR_POLARITY BIT(0)
> @@ -191,6 +194,12 @@
> #define TEGRA_SMC_PMC_READ 0xaa
> #define TEGRA_SMC_PMC_WRITE 0xbb
>
> +#define WAKE_NR_EVENTS 96
> +#define WAKE_NR_VECTORS (WAKE_NR_EVENTS / 32)

Are these always 96? Can they not vary by SoC generation?

> +
> +DECLARE_BITMAP(wake_level_map, WAKE_NR_EVENTS);
> +DECLARE_BITMAP(wake_dual_edge_map, WAKE_NR_EVENTS);

These should not be global variables but rather part of struct pmc.
Might be worth to allocate them dynamically based on a parameterized
per-SoC num_events.

> +
> struct pmc_clk {
> struct clk_hw hw;
> unsigned long offs;
> @@ -2469,29 +2478,37 @@ static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type)
> {
> struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
> u32 value;
> + unsigned long wake_id;

The usefulness of that local variable is questionable. Might as well
just keep using data->hwirq.

>
> - value = readl(pmc->wake + WAKE_AOWAKE_CNTRL(data->hwirq));
> + wake_id = data->hwirq;
> + value = readl(pmc->wake + WAKE_AOWAKE_CNTRL(wake_id));
>
> switch (type) {
> case IRQ_TYPE_EDGE_RISING:
> case IRQ_TYPE_LEVEL_HIGH:
> value |= WAKE_AOWAKE_CNTRL_LEVEL;
> + set_bit(wake_id, wake_level_map);
> + clear_bit(wake_id, wake_dual_edge_map);
> break;
>
> case IRQ_TYPE_EDGE_FALLING:
> case IRQ_TYPE_LEVEL_LOW:
> value &= ~WAKE_AOWAKE_CNTRL_LEVEL;
> + clear_bit(wake_id, wake_level_map);
> + clear_bit(wake_id, wake_dual_edge_map);
> break;
>
> case IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING:
> value ^= WAKE_AOWAKE_CNTRL_LEVEL;
> + clear_bit(wake_id, wake_level_map);
> + set_bit(wake_id, wake_dual_edge_map);
> break;
>
> default:
> return -EINVAL;
> }
>
> - writel(value, pmc->wake + WAKE_AOWAKE_CNTRL(data->hwirq));
> + writel(value, pmc->wake + WAKE_AOWAKE_CNTRL(wake_id));
>
> return 0;
> }
> @@ -3074,28 +3091,142 @@ static int tegra_pmc_probe(struct platform_device *pdev)
> return err;
> }
>
> -#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM)
> +#ifdef CONFIG_PM_SLEEP
> +#ifdef CONFIG_ARM64
> +/*
> + * Ensures that sufficient time is passed for a register write to
> + * serialize into the 32KHz domain.
> + */
> +static void wke_32kwritel(u32 val, u32 reg)
> +{
> + writel(val, pmc->wake + reg);
> + udelay(130);
> +}
> +
> +static void wke_write_wake_level(int wake, int level)
> +{
> + u32 val;
> + u32 reg = WAKE_AOWAKE_CNTRL(wake);
> +
> + val = readl(pmc->wake + reg);
> + if (level)
> + val |= WAKE_AOWAKE_CNTRL_LEVEL;
> + else
> + val &= ~WAKE_AOWAKE_CNTRL_LEVEL;
> + writel(val, pmc->wake + reg);
> +}
> +
> +static void wke_write_wake_levels(unsigned long *lvl)
> +{
> + int i;
> +
> + for (i = 0; i < WAKE_NR_EVENTS; i++)
> + wke_write_wake_level(i, test_bit(i, lvl));
> +}
> +
> +static void wke_clear_sw_wake_status(void)
> +{
> + wke_32kwritel(1, WAKE_AOWAKE_SW_STATUS_W_0);
> +}
> +
> +static void wke_read_sw_wake_status(unsigned long *status_map)
> +{
> + u32 status[WAKE_NR_VECTORS];
> + int i;
> +
> + for (i = 0; i < WAKE_NR_EVENTS; i++)
> + wke_write_wake_level(i, 0);
> +
> + wke_clear_sw_wake_status();
> +
> + wke_32kwritel(1, WAKE_LATCH_SW);
> +
> + /*
> + * WAKE_AOWAKE_SW_STATUS is edge triggered, so in order to
> + * obtain the current status of the input wake signals, change
> + * the polarity of the wake level from 0->1 while latching to force
> + * a positive edge if the sampled signal is '1'.
> + */
> + for (i = 0; i < WAKE_NR_EVENTS; i++)
> + wke_write_wake_level(i, 1);
> +
> + /*
> + * Wait for the update to be synced into the 32kHz domain,
> + * and let enough time lapse, so that the wake signals have time to
> + * be sampled.
> + */
> + udelay(300);
> +
> + wke_32kwritel(0, WAKE_LATCH_SW);
> +
> + for (i = 0; i < WAKE_NR_VECTORS; i++)
> + status[i] = readl(pmc->wake + WAKE_AOWAKE_SW_STATUS(i));
> +
> + bitmap_from_arr32(status_map, status, WAKE_NR_EVENTS);
> +}
> +
> +static void wke_clear_wake_status(void)
> +{
> + u32 status;
> + int i, wake;
> + unsigned long ulong_status;
> +
> + for (i = 0; i < WAKE_NR_VECTORS; i++) {
> + status = readl(pmc->wake + WAKE_AOWAKE_STATUS_R(i));
> + status = status & readl(pmc->wake +
> + WAKE_AOWAKE_TIER2_ROUTING(i));
> + ulong_status = (unsigned long)status;
> + for_each_set_bit(wake, &ulong_status, 32)
> + wke_32kwritel(0x1,
> + WAKE_AOWAKE_STATUS_W((i * 32) + wake));
> + }
> +}
> +#endif /* CONFIG_ARM64 */
> +
> static int tegra_pmc_suspend(struct device *dev)
> {
> +#ifdef CONFIG_ARM
> struct tegra_pmc *pmc = dev_get_drvdata(dev);
>
> tegra_pmc_writel(pmc, virt_to_phys(tegra_resume), PMC_SCRATCH41);

If this code is really no longer needed on 64-bit ARM, this should be a
separate patch. Also, these conditionals are becoming a bit unwieldy, so
I wonder if we should instead move to C conditionals using IS_ENABLED()
to make this a bit more readable and get improved code coverage at
compile time.

> +#else /* CONFIG_ARM64 */

The implied condition here seems a bit broad. I think it might be better
to conditionalize this code on the setting of tegra_pmc_soc.wake_events.
If that's != NULL, then execute all this context store/restore,
otherwise don't.

> + DECLARE_BITMAP(status, WAKE_NR_EVENTS);
> + DECLARE_BITMAP(lvl, WAKE_NR_EVENTS);
> + DECLARE_BITMAP(wake_level, WAKE_NR_EVENTS);
> + int i;
> +
> + wke_read_sw_wake_status(status);
> +
> + /* flip the wakeup trigger for dual-edge triggered pads
> + * which are currently asserting as wakeups
> + */
> + for (i = 0; i < BITS_TO_LONGS(WAKE_NR_EVENTS); i++) {
> + lvl[i] = ~status[i] & wake_dual_edge_map[i];
> + wake_level[i] = lvl[i] | wake_level_map[i];
> + }

I think these can be done using:

bitmap_andnot(lvl, wake_dual_edge_map, status, WAKE_NR_EVENTS);
bitmap_or(wake_level, lvl, wake_level_map, WAKE_NR_EVENTS);

> +
> + /* Clear PMC Wake Status registers while going to suspend */
> + wke_clear_wake_status();
>
> + wke_write_wake_levels(wake_level);
> +#endif
> return 0;
> }
>
> static int tegra_pmc_resume(struct device *dev)
> {
> +#ifdef CONFIG_ARM
> struct tegra_pmc *pmc = dev_get_drvdata(dev);
>
> tegra_pmc_writel(pmc, 0x0, PMC_SCRATCH41);
> +#endif
>
> return 0;
> }
>
> static SIMPLE_DEV_PM_OPS(tegra_pmc_pm_ops, tegra_pmc_suspend, tegra_pmc_resume);
>
> -#endif
> +#endif /*CONFIG_PM_SLEEP */

Perhaps we should then also drop the CONFIG_PM_SLEEP conditional and
instead mark tegra_pmc_suspend()/tegra_pmc_resume() as __maybe_unused.
Could also be a separate patch.

Thierry

>
> static const char * const tegra20_powergates[] = {
> [TEGRA_POWERGATE_CPU] = "cpu",
> @@ -4069,7 +4200,7 @@ static struct platform_driver tegra_pmc_driver = {
> .name = "tegra-pmc",
> .suppress_bind_attrs = true,
> .of_match_table = tegra_pmc_match,
> -#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM)
> +#if defined(CONFIG_PM_SLEEP)
> .pm = &tegra_pmc_pm_ops,
> #endif
> .sync_state = tegra_pmc_sync_state,
> --
> 2.17.1
>

Attachment: signature.asc
Description: PGP signature