Skip to content

Commit 1e29fb2

Browse files
committed
ARM: at91: pm: add quirks for pm
SoCs supporting ULP0 or ULP1 modes and variants of Cadence Ethernet IP (controlled by macb driver) may behave buggy when Wake-on-Lan (WoL) is configured and WoL packet is received while in ULP0/ULP1. On some SoCs Ethernet interface is not working after resume. On other SoCs the CPU goes to abort on resume path when switching execution from internal SRAM to DRAM. For ULP1 + WoL the issue is related a particular restart sequence of the internal clocks when resuming. These clocks are automatically managed by PMC and may happen that GMAC peripheral clock is restarted few clock cycles before internal clocks causing blocking of Ethernet's DMA. As a consequence Ethernet TX transactions are stopped and RX transactions are partially stopped (packets are received by MAC, RX counters incremented but the data is not transferred to DRAM). The workaround for this is to disable Ethernet's peripheral clock when going to ULP1. Same behavior has been reproduced on ULP0 for some platforms (SAMA5D2, SAMA5D3), the same workaround solves the issue but at the moment there is no investigation on design path to clarify if the problem is the same as in case of ULP1. The problem has been solved on pm.c as quirk to avoid polluting the MACB driver with AT91 specific issues as this driver is generic to multiple vendors. At probe pointers to struct device_node are retrieved and on the at91_pm_enter() the quirk specifics are applied: for all Ethernet interfaces that were parsed the peripheral clocks are disabled. A special handling is done for modes in dns_modes mask as these are considered modes that blocks the system if WoL packet are received but for which applying quirk will lead to not waking up on WoL packets: in situation where Ethernet interface(s) has suspend mode in dns_modes mask and Ethernet interface(s) is the only available wakeup source the suspend is canceled. Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
1 parent b5d7976 commit 1e29fb2

1 file changed

Lines changed: 263 additions & 2 deletions

File tree

  • arch/arm/mach-at91

arch/arm/mach-at91/pm.c

Lines changed: 263 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <linux/parser.h>
1616
#include <linux/suspend.h>
1717

18+
#include <linux/clk.h>
1819
#include <linux/clk/at91_pmc.h>
1920
#include <linux/platform_data/atmel.h>
2021

@@ -60,13 +61,61 @@ struct at91_pm_sfrbu_regs {
6061
} pswbu;
6162
};
6263

64+
/*
65+
* enum at91_pm_eth_clk: Ethernet clock indexes
66+
* @AT91_PM_ETH_PCLK: pclk index
67+
* @AT91_PM_ETH_HCLK: hclk index
68+
* @AT91_PM_ETH_MAX_CLK: max index
69+
*/
70+
enum at91_pm_eth_clk {
71+
AT91_PM_ETH_PCLK,
72+
AT91_PM_ETH_HCLK,
73+
AT91_PM_ETH_MAX_CLK,
74+
};
75+
76+
/*
77+
* enum at91_pm_eth: Ethernet controller indexes
78+
* @AT91_PM_G_ETH: gigabit Ethernet controller index
79+
* @AT91_PM_E_ETH: megabit Ethernet controller index
80+
* @AT91_PM_MAX_ETH: max index
81+
*/
82+
enum at91_pm_eth {
83+
AT91_PM_G_ETH,
84+
AT91_PM_E_ETH,
85+
AT91_PM_MAX_ETH,
86+
};
87+
88+
/*
89+
* @np: Ethernet device node
90+
* @clks: Ethernet clocks
91+
* @modes: power management mode that this quirk applies to
92+
* @dns_modes: do not suspend modes: stop suspending if Ethernet is configured
93+
* as wakeup source but buggy and no other wakeup source is
94+
* available
95+
*/
96+
struct at91_pm_quirk_eth {
97+
struct device_node *np;
98+
struct clk_bulk_data clks[AT91_PM_ETH_MAX_CLK];
99+
u32 modes;
100+
u32 dns_modes;
101+
};
102+
103+
/*
104+
* struct at91_pm_quirks: AT91 specific quirks
105+
* @eth: Ethernet quirks
106+
*/
107+
struct at91_pm_quirks {
108+
struct at91_pm_quirk_eth eth[AT91_PM_MAX_ETH];
109+
};
110+
63111
/*
64112
* struct at91_soc_pm - AT91 SoC power management data structure
65113
* @config_shdwc_ws: wakeup sources configuration function for SHDWC
66114
* @config_pmc_ws: wakeup srouces configuration function for PMC
67115
* @ws_ids: wakup sources of_device_id array
68116
* @shdwc_np: pointer to shdwc node
69117
* @bu: backup unit mapped data (for backup mode)
118+
* @quirks: PM quirks
70119
* @data: PM data to be used on last phase of suspend
71120
* @sfrbu_regs: SFRBU registers mapping
72121
* @memcs: memory chip select
@@ -77,6 +126,7 @@ struct at91_soc_pm {
77126
const struct of_device_id *ws_ids;
78127
struct device_node *shdwc_np;
79128
struct at91_pm_bu *bu;
129+
struct at91_pm_quirks quirks;
80130
struct at91_pm_data data;
81131
struct at91_pm_sfrbu_regs sfrbu_regs;
82132
void *memcs;
@@ -86,10 +136,12 @@ struct at91_soc_pm {
86136
* enum at91_pm_iomaps: IOs that needs to be mapped for different PM modes
87137
* @AT91_PM_IOMAP_SHDWC: SHDWC controller
88138
* @AT91_PM_IOMAP_SFRBU: SFRBU controller
139+
* @AT91_PM_IOMAP_ETHC: Ethernet controller
89140
*/
90141
enum at91_pm_iomaps {
91142
AT91_PM_IOMAP_SHDWC,
92143
AT91_PM_IOMAP_SFRBU,
144+
AT91_PM_IOMAP_ETHC,
93145
};
94146

95147
#define AT91_PM_IOMAP(name) BIT(AT91_PM_IOMAP_##name)
@@ -343,6 +395,115 @@ static int at91_sam9x60_config_pmc_ws(void __iomem *pmc, u32 mode, u32 polarity)
343395
return 0;
344396
}
345397

398+
static bool at91_pm_eth_quirk_is_valid(int index)
399+
{
400+
struct platform_device *pdev;
401+
bool ret = true;
402+
403+
/* Interface NA in DT. */
404+
if (!soc_pm.quirks.eth[index].np)
405+
return false;
406+
407+
/* No quirks for this interface and current suspend mode. */
408+
if (!(soc_pm.quirks.eth[index].modes & BIT(soc_pm.data.mode)))
409+
return false;
410+
411+
/* Driver not probed. */
412+
pdev = of_find_device_by_node(soc_pm.quirks.eth[index].np);
413+
if (!pdev)
414+
return false;
415+
416+
/* No quirks if device isn't a wakeup source. */
417+
if (!device_may_wakeup(&pdev->dev))
418+
ret = false;
419+
420+
put_device(&pdev->dev);
421+
return ret;
422+
}
423+
424+
static int at91_pm_config_quirks(bool suspend)
425+
{
426+
struct wakeup_source *ws;
427+
int i, j, ret, tmp;
428+
429+
/*
430+
* Ethernet IPs who's device_node pointers are stored into
431+
* soc_pm.quirks.eth[].np cannot handle WoL packets while in ULP0, ULP1
432+
* or both due to a hardware bug. If they receive WoL packets while in
433+
* ULP0 or ULP1 IPs could stop working or the whole system could stop
434+
* working. We cannot handle this scenario in the ethernet driver itself
435+
* as the driver is common to multiple vendors and also we only know
436+
* here, in this file, if we suspend to ULP0 or ULP1 mode. Thus handle
437+
* these scenarios here, as quirks.
438+
*/
439+
for (i = 0; i < AT91_PM_MAX_ETH; i++) {
440+
if (!at91_pm_eth_quirk_is_valid(i))
441+
continue;
442+
443+
/*
444+
* For modes in dns_modes mask the system blocks if quirk is not
445+
* applied but if applied the interface doesn't act at WoL
446+
* events. Thus take care to avoid suspending if this interface
447+
* is the only configured wakeup source.
448+
*/
449+
if (suspend &&
450+
soc_pm.quirks.eth[i].dns_modes & BIT(soc_pm.data.mode)) {
451+
int ws_count = 0;
452+
453+
for_each_wakeup_source(ws) {
454+
ws_count++;
455+
if (ws_count > 1)
456+
break;
457+
}
458+
459+
/*
460+
* Checking ws == 1 is good for all SAMA5 based platforms
461+
* even when both G_ETH and E_ETH are available as dsn_modes
462+
* is populated only on G_ETH interface.
463+
*/
464+
if (ws_count == 1) {
465+
pr_err("AT91: PM: Ethernet cannot resume from WoL!");
466+
ret = -EPERM;
467+
goto clk_unconfigure;
468+
}
469+
}
470+
471+
if (suspend) {
472+
clk_bulk_disable_unprepare(AT91_PM_ETH_MAX_CLK,
473+
soc_pm.quirks.eth[i].clks);
474+
} else {
475+
ret = clk_bulk_prepare_enable(AT91_PM_ETH_MAX_CLK,
476+
soc_pm.quirks.eth[i].clks);
477+
if (ret)
478+
goto clk_unconfigure;
479+
}
480+
}
481+
482+
return 0;
483+
484+
clk_unconfigure:
485+
/*
486+
* In case of resume we reach this point if clk_prepare_enable() failed.
487+
* we don't want to revert the previous clk_prepare_enable() for the
488+
* other IP.
489+
*/
490+
if (suspend) {
491+
for (j = i - 1; j <= 0; j--) {
492+
if (!at91_pm_eth_quirk_is_valid(i))
493+
continue;
494+
495+
tmp = clk_bulk_prepare_enable(AT91_PM_ETH_MAX_CLK,
496+
soc_pm.quirks.eth[i].clks);
497+
if (tmp) {
498+
pr_err("AT91: PM: failed to enable %s clock\n",
499+
i == AT91_PM_ETH_PCLK ? "pclk" : "hclk");
500+
}
501+
}
502+
}
503+
504+
return ret;
505+
}
506+
346507
/*
347508
* Called after processes are frozen, but before we shutdown devices.
348509
*/
@@ -514,6 +675,12 @@ static void at91_pm_suspend(suspend_state_t state)
514675
*/
515676
static int at91_pm_enter(suspend_state_t state)
516677
{
678+
int ret;
679+
680+
ret = at91_pm_config_quirks(true);
681+
if (ret)
682+
return ret;
683+
517684
#ifdef CONFIG_PINCTRL_AT91
518685
/*
519686
* FIXME: this is needed to communicate between the pinctrl driver and
@@ -551,6 +718,7 @@ static int at91_pm_enter(suspend_state_t state)
551718
#ifdef CONFIG_PINCTRL_AT91
552719
at91_pinctrl_gpio_resume();
553720
#endif
721+
at91_pm_config_quirks(false);
554722
return 0;
555723
}
556724

@@ -948,6 +1116,20 @@ static const struct of_device_id atmel_shdwc_ids[] = {
9481116
{ /* sentinel. */ }
9491117
};
9501118

1119+
static const struct of_device_id gmac_ids[] __initconst = {
1120+
{ .compatible = "atmel,sama5d3-gem" },
1121+
{ .compatible = "atmel,sama5d2-gem" },
1122+
{ .compatible = "atmel,sama5d29-gem" },
1123+
{ .compatible = "microchip,sama7g5-gem" },
1124+
{ },
1125+
};
1126+
1127+
static const struct of_device_id emac_ids[] __initconst = {
1128+
{ .compatible = "atmel,sama5d3-macb" },
1129+
{ .compatible = "microchip,sama7g5-emac" },
1130+
{ },
1131+
};
1132+
9511133
/*
9521134
* Replaces _mode_to_replace with a supported mode that doesn't depend
9531135
* on controller pointed by _map_bitmask
@@ -1001,8 +1183,30 @@ static const struct of_device_id atmel_shdwc_ids[] = {
10011183
(soc_pm.data.standby_mode)); \
10021184
} while (0)
10031185

1186+
static int __init at91_pm_get_eth_clks(struct device_node *np,
1187+
struct clk_bulk_data *clks)
1188+
{
1189+
clks[AT91_PM_ETH_PCLK].clk = of_clk_get_by_name(np, "pclk");
1190+
if (IS_ERR(clks[AT91_PM_ETH_PCLK].clk))
1191+
return PTR_ERR(clks[AT91_PM_ETH_PCLK].clk);
1192+
1193+
clks[AT91_PM_ETH_HCLK].clk = of_clk_get_by_name(np, "hclk");
1194+
if (IS_ERR(clks[AT91_PM_ETH_HCLK].clk))
1195+
return PTR_ERR(clks[AT91_PM_ETH_HCLK].clk);
1196+
1197+
return 0;
1198+
}
1199+
1200+
static int __init at91_pm_eth_clks_empty(struct clk_bulk_data *clks)
1201+
{
1202+
return IS_ERR(clks[AT91_PM_ETH_PCLK].clk) ||
1203+
IS_ERR(clks[AT91_PM_ETH_HCLK].clk);
1204+
}
1205+
10041206
static void __init at91_pm_modes_init(const u32 *maps, int len)
10051207
{
1208+
struct at91_pm_quirk_eth *gmac = &soc_pm.quirks.eth[AT91_PM_G_ETH];
1209+
struct at91_pm_quirk_eth *emac = &soc_pm.quirks.eth[AT91_PM_E_ETH];
10061210
struct device_node *np;
10071211
int ret;
10081212

@@ -1042,6 +1246,40 @@ static void __init at91_pm_modes_init(const u32 *maps, int len)
10421246
}
10431247
}
10441248

1249+
if ((at91_is_pm_mode_active(AT91_PM_ULP1) ||
1250+
at91_is_pm_mode_active(AT91_PM_ULP0)) &&
1251+
(maps[soc_pm.data.standby_mode] & AT91_PM_IOMAP(ETHC) ||
1252+
maps[soc_pm.data.suspend_mode] & AT91_PM_IOMAP(ETHC))) {
1253+
np = of_find_matching_node(NULL, gmac_ids);
1254+
if (!np) {
1255+
np = of_find_matching_node(NULL, emac_ids);
1256+
if (np)
1257+
goto get_emac_clks;
1258+
AT91_PM_REPLACE_MODES(maps, ETHC);
1259+
goto unmap_unused_nodes;
1260+
} else {
1261+
gmac->np = np;
1262+
at91_pm_get_eth_clks(np, gmac->clks);
1263+
}
1264+
1265+
np = of_find_matching_node(NULL, emac_ids);
1266+
if (!np) {
1267+
if (at91_pm_eth_clks_empty(gmac->clks))
1268+
AT91_PM_REPLACE_MODES(maps, ETHC);
1269+
} else {
1270+
get_emac_clks:
1271+
emac->np = np;
1272+
ret = at91_pm_get_eth_clks(np, emac->clks);
1273+
if (ret && at91_pm_eth_clks_empty(gmac->clks)) {
1274+
of_node_put(gmac->np);
1275+
of_node_put(emac->np);
1276+
gmac->np = NULL;
1277+
emac->np = NULL;
1278+
}
1279+
}
1280+
}
1281+
1282+
unmap_unused_nodes:
10451283
/* Unmap all unnecessary. */
10461284
if (soc_pm.data.shdwc &&
10471285
!(maps[soc_pm.data.standby_mode] & AT91_PM_IOMAP(SHDWC) ||
@@ -1277,17 +1515,27 @@ void __init sama5_pm_init(void)
12771515
static const int modes[] __initconst = {
12781516
AT91_PM_STANDBY, AT91_PM_ULP0, AT91_PM_ULP0_FAST,
12791517
};
1518+
static const u32 iomaps[] __initconst = {
1519+
[AT91_PM_ULP0] = AT91_PM_IOMAP(ETHC),
1520+
};
12801521
int ret;
12811522

12821523
if (!IS_ENABLED(CONFIG_SOC_SAMA5))
12831524
return;
12841525

12851526
at91_pm_modes_validate(modes, ARRAY_SIZE(modes));
1527+
at91_pm_modes_init(iomaps, ARRAY_SIZE(iomaps));
12861528
ret = at91_dt_ramc(false);
12871529
if (ret)
12881530
return;
12891531

12901532
at91_pm_init(NULL);
1533+
1534+
/* Quirks applies to ULP0 and ULP1 modes. */
1535+
soc_pm.quirks.eth[AT91_PM_G_ETH].modes = BIT(AT91_PM_ULP0) |
1536+
BIT(AT91_PM_ULP1);
1537+
/* Do not suspend in ULP0 if GETH is the only wakeup source. */
1538+
soc_pm.quirks.eth[AT91_PM_G_ETH].dns_modes = BIT(AT91_PM_ULP0);
12911539
}
12921540

12931541
void __init sama5d2_pm_init(void)
@@ -1297,7 +1545,9 @@ void __init sama5d2_pm_init(void)
12971545
AT91_PM_BACKUP,
12981546
};
12991547
static const u32 iomaps[] __initconst = {
1300-
[AT91_PM_ULP1] = AT91_PM_IOMAP(SHDWC),
1548+
[AT91_PM_ULP0] = AT91_PM_IOMAP(ETHC),
1549+
[AT91_PM_ULP1] = AT91_PM_IOMAP(SHDWC) |
1550+
AT91_PM_IOMAP(ETHC),
13011551
[AT91_PM_BACKUP] = AT91_PM_IOMAP(SHDWC) |
13021552
AT91_PM_IOMAP(SFRBU),
13031553
};
@@ -1322,6 +1572,12 @@ void __init sama5d2_pm_init(void)
13221572
soc_pm.sfrbu_regs.pswbu.ctrl = BIT(0);
13231573
soc_pm.sfrbu_regs.pswbu.softsw = BIT(1);
13241574
soc_pm.sfrbu_regs.pswbu.state = BIT(3);
1575+
1576+
/* Quirk applies to ULP0 and ULP1 modes. */
1577+
soc_pm.quirks.eth[AT91_PM_G_ETH].modes = BIT(AT91_PM_ULP0) |
1578+
BIT(AT91_PM_ULP1);
1579+
/* Do not suspend in ULP0 if GETH is the only wakeup source. */
1580+
soc_pm.quirks.eth[AT91_PM_G_ETH].dns_modes = BIT(AT91_PM_ULP0);
13251581
}
13261582

13271583
void __init sama7_pm_init(void)
@@ -1333,7 +1589,8 @@ void __init sama7_pm_init(void)
13331589
[AT91_PM_ULP0] = AT91_PM_IOMAP(SFRBU) |
13341590
AT91_PM_IOMAP(SHDWC),
13351591
[AT91_PM_ULP1] = AT91_PM_IOMAP(SFRBU) |
1336-
AT91_PM_IOMAP(SHDWC),
1592+
AT91_PM_IOMAP(SHDWC) |
1593+
AT91_PM_IOMAP(ETHC),
13371594
[AT91_PM_BACKUP] = AT91_PM_IOMAP(SFRBU) |
13381595
AT91_PM_IOMAP(SHDWC),
13391596
};
@@ -1358,6 +1615,10 @@ void __init sama7_pm_init(void)
13581615
soc_pm.sfrbu_regs.pswbu.ctrl = BIT(0);
13591616
soc_pm.sfrbu_regs.pswbu.softsw = BIT(1);
13601617
soc_pm.sfrbu_regs.pswbu.state = BIT(2);
1618+
1619+
/* Quirks applies to ULP1 for both Ethernet interfaces. */
1620+
soc_pm.quirks.eth[AT91_PM_E_ETH].modes = BIT(AT91_PM_ULP1);
1621+
soc_pm.quirks.eth[AT91_PM_G_ETH].modes = BIT(AT91_PM_ULP1);
13611622
}
13621623

13631624
static int __init at91_pm_modes_select(char *str)

0 commit comments

Comments
 (0)