diff options
| author | Marc Kleine-Budde <mkl@pengutronix.de> | 2025-10-17 12:02:57 +0300 |
|---|---|---|
| committer | Marc Kleine-Budde <mkl@pengutronix.de> | 2025-10-17 12:02:57 +0300 |
| commit | 578dbbb9524cfc2f28b03df6d91eda9454575eb2 (patch) | |
| tree | 8a02d22487137b2da18f6ef48a642426b6ec9d10 /drivers | |
| parent | f968a24cad3da72fdff12a0ae5ac0b679439cca1 (diff) | |
| parent | a77a29775373a91ad1ca0375e0ab1b1b294d53bb (diff) | |
| download | linux-578dbbb9524cfc2f28b03df6d91eda9454575eb2.tar.xz | |
Merge patch series "can: m_can: Add am62 wakeup support"
Markus Schneider-Pargmann (TI.com) <msp@baylibre.com> says:
This series adds support for wakeup capabilities to the m_can driver,
which is necessary for enabling Partial-IO functionality on am62, am62a,
and am62p SoCs. It implements the wake-on-lan interface for m_can
devices and handles the pinctrl states needed for wakeup functionality.
am62, am62a and am62p support Partial-IO, a low power system state in
which nearly everything is turned off except the pins of the CANUART
group. This group contains mcu_mcan0, mcu_mcan1, wkup_uart0 and
mcu_uart0 devices.
To support mcu_mcan0 and mcu_mcan1 wakeup for the mentioned SoCs, the
series introduces a notion of wake-on-lan for m_can. If the user decides
to enable wake-on-lan for a m_can device, the device is set to wakeup
enabled. A 'wakeup' pinctrl state is selected to enable wakeup flags for
the relevant pins. If wake-on-lan is disabled the default pinctrl is
selected.
Partial-IO Overview
------------------
Partial-IO is a low power system state in which nearly everything is
turned off except the pins of the CANUART group (mcu_mcan0, mcu_mcan1,
wkup_uart0 and mcu_uart0). These devices can trigger a wakeup of the
system on pin activity. Note that this does not resume the system as the
DDR is off as well. So this state can be considered a power-off state
with wakeup capabilities.
A documentation can also be found in section 6.2.4 in the TRM:
https://www.ti.com/lit/pdf/spruiv7
Implementation Details
----------------------
The complete Partial-IO feature requires three coordinated series, each
handling a different aspect of the implementation:
1. This series (m_can driver): Implements device-specific wakeup
functionality for m_can devices, allowing them to be set as wakeup
sources.
2. Devicetree series: Defines system states and wakeup sources in the
devicetree for am62, am62a and am62p.
https://gitlab.baylibre.com/msp8/linux/-/tree/topic/am62-dt-partialio/v6.17?ref_type=heads
3. TI-SCI firmware series: Implements the firmware interface to enter
Partial-IO mode when appropriate wakeup sources are enabled.
https://gitlab.baylibre.com/msp8/linux/-/tree/topic/tisci-partialio/v6.17?ref_type=heads
Devicetree Bindings
-------------------
The wakeup-source property is used with references to
system-idle-states. This depends on the dt-schema pull request that adds
bindings for system-idle-states and updates the binding for
wakeup-source:
https://github.com/devicetree-org/dt-schema/pull/150
This is merged now and upstream in dt-schema.
Testing
-------
A test branch is available here that includes all patches required to
test Partial-IO:
https://gitlab.baylibre.com/msp8/linux/-/tree/integration/am62-partialio/v6.17?ref_type=heads
After enabling Wake-on-LAN the system can be powered off and will enter
the Partial-IO state in which it can be woken up by activity on the
specific pins:
ethtool -s can0 wol p
ethtool -s can1 wol p
poweroff
I tested these patches on am62-lp-sk.
Previous versions:
v1: https://lore.kernel.org/lkml/20240523075347.1282395-1-msp@baylibre.com/
v2: https://lore.kernel.org/lkml/20240729074135.3850634-1-msp@baylibre.com/
v3: https://lore.kernel.org/lkml/20241011-topic-mcan-wakeup-source-v6-12-v3-0-9752c714ad12@baylibre.com
v4: https://lore.kernel.org/r/20241015-topic-mcan-wakeup-source-v6-12-v4-0-fdac1d1e7aa6@baylibre.com
v5: https://lore.kernel.org/r/20241028-topic-mcan-wakeup-source-v6-12-v5-0-33edc0aba629@baylibre.com
v6: https://lore.kernel.org/r/20241219-topic-mcan-wakeup-source-v6-12-v6-0-1356c7f7cfda@baylibre.com
v7: https://lore.kernel.org/r/20250421-topic-mcan-wakeup-source-v6-12-v7-0-1b7b916c9832@baylibre.com
v8: https://lore.kernel.org/r/20250812-topic-mcan-wakeup-source-v6-12-v8-0-6972a810d63b@baylibre.com
v9: https://lore.kernel.org/r/20250820-topic-mcan-wakeup-source-v6-12-v9-0-0ac13f2ddd67@baylibre.com
Changes in v10:
- Change dt-binding to be able to set pinctrl-names = "default", "wakeup";
- Fix wording in the dt-binging
- Fix mcan commit message to have correct naming of the SoC
- Change function name from m_can_class_setup_optional_pinctrl() to
m_can_class_parse_pinctrl()
Changes in v9:
- Update the binding to accept the sleep pinctrl state which is
already in use by other devicetrees
- Modify suspend/resume to not set the sleep state if wakeup is enabled
and a wakeup pinctrl state is present. If wakeup pinctrl is active
this should be kept enabled even after suspend
- Modify m_can_set_wol() to use pinctrl_pm_select_default_state() to
get rid of the manually managed default pinctrl.
Changes in v8:
- Rebase to v6.17-rc1
Changes in v7:
- Separate this series from "firmware: ti_sci: Partial-IO support"
again as was requested internally
- All DT changes are now in their own series to avoid conflicts
- wakeup-source definition in the m_can binding is now only an
extension to the dt-schema binding and a pull request was created
Changes in v6:
- Rebased to v6.13-rc1
- After feedback of the other Partial-IO series, I updated this series
and removed all use of regulator-related patches.
- wakeup-source is now not only a boolean property but can also be a
list of power states in which the device is wakeup capable.
Changes in v5:
- Make the check of wol options nicer to read
Changes in v4:
- Remove leftover testing code that always returned -EIO in a specific
- Redesign pincontrol setup to be easier understandable and less nested
- Fix missing parantheses around wol_enable expression
- Remove | from binding description
Changes in v3:
- Rebase to v6.12-rc1
- Change 'wakeup-source' to only 'true'
- Simplify m_can_set_wol by returning early on error
- Add vio-suuply binding and handling of this optional property.
vio-supply is used to reflect the SoC architecture and which power
line powers the m_can unit. This is important as some units are
powered in special low power modes.
Changes in v2:
- Rebase to v6.11-rc1
- Squash these two patches for the binding into one:
dt-bindings: can: m_can: Add wakeup-source property
dt-bindings: can: m_can: Add wakeup pinctrl state
- Add error handling to multiple patches of the m_can driver
- Add error handling in m_can_class_allocate_dev(). This also required
to add a new patch to return error pointers from
m_can_class_allocate_dev().
Link: https://patch.msgid.link/20251001-topic-mcan-wakeup-source-v6-12-v10-0-4ab508ac5d1e@baylibre.com
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/net/can/m_can/m_can.c | 112 | ||||
| -rw-r--r-- | drivers/net/can/m_can/m_can.h | 3 | ||||
| -rw-r--r-- | drivers/net/can/m_can/m_can_pci.c | 4 | ||||
| -rw-r--r-- | drivers/net/can/m_can/m_can_platform.c | 4 | ||||
| -rw-r--r-- | drivers/net/can/m_can/tcan4x5x-core.c | 4 |
5 files changed, 116 insertions, 11 deletions
diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index 873f5991fc5a..8569596ae830 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -2238,6 +2238,55 @@ static int m_can_set_coalesce(struct net_device *dev, return 0; } +static void m_can_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ + struct m_can_classdev *cdev = netdev_priv(dev); + + wol->supported = device_can_wakeup(cdev->dev) ? WAKE_PHY : 0; + wol->wolopts = device_may_wakeup(cdev->dev) ? WAKE_PHY : 0; +} + +static int m_can_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ + struct m_can_classdev *cdev = netdev_priv(dev); + bool wol_enable = !!(wol->wolopts & WAKE_PHY); + int ret; + + if (wol->wolopts & ~WAKE_PHY) + return -EINVAL; + + if (wol_enable == device_may_wakeup(cdev->dev)) + return 0; + + ret = device_set_wakeup_enable(cdev->dev, wol_enable); + if (ret) { + netdev_err(cdev->net, "Failed to set wakeup enable %pE\n", + ERR_PTR(ret)); + return ret; + } + + if (!IS_ERR_OR_NULL(cdev->pinctrl_state_wakeup)) { + if (wol_enable) + ret = pinctrl_select_state(cdev->pinctrl, cdev->pinctrl_state_wakeup); + else + ret = pinctrl_pm_select_default_state(cdev->dev); + + if (ret) { + netdev_err(cdev->net, "Failed to select pinctrl state %pE\n", + ERR_PTR(ret)); + goto err_wakeup_enable; + } + } + + return 0; + +err_wakeup_enable: + /* Revert wakeup enable */ + device_set_wakeup_enable(cdev->dev, !wol_enable); + + return ret; +} + static const struct ethtool_ops m_can_ethtool_ops_coalescing = { .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS_IRQ | ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ | @@ -2247,10 +2296,14 @@ static const struct ethtool_ops m_can_ethtool_ops_coalescing = { .get_ts_info = ethtool_op_get_ts_info, .get_coalesce = m_can_get_coalesce, .set_coalesce = m_can_set_coalesce, + .get_wol = m_can_get_wol, + .set_wol = m_can_set_wol, }; static const struct ethtool_ops m_can_ethtool_ops = { .get_ts_info = ethtool_op_get_ts_info, + .get_wol = m_can_get_wol, + .set_wol = m_can_set_wol, }; static int register_m_can_dev(struct m_can_classdev *cdev) @@ -2359,6 +2412,42 @@ int m_can_class_get_clocks(struct m_can_classdev *cdev) } EXPORT_SYMBOL_GPL(m_can_class_get_clocks); +static bool m_can_class_wakeup_pinctrl_enabled(struct m_can_classdev *class_dev) +{ + return device_may_wakeup(class_dev->dev) && class_dev->pinctrl_state_wakeup; +} + +static int m_can_class_parse_pinctrl(struct m_can_classdev *class_dev) +{ + struct device *dev = class_dev->dev; + int ret; + + class_dev->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(class_dev->pinctrl)) { + ret = PTR_ERR(class_dev->pinctrl); + class_dev->pinctrl = NULL; + + if (ret == -ENODEV) + return 0; + + return dev_err_probe(dev, ret, "Failed to get pinctrl\n"); + } + + class_dev->pinctrl_state_wakeup = + pinctrl_lookup_state(class_dev->pinctrl, "wakeup"); + if (IS_ERR(class_dev->pinctrl_state_wakeup)) { + ret = PTR_ERR(class_dev->pinctrl_state_wakeup); + class_dev->pinctrl_state_wakeup = NULL; + + if (ret == -ENODEV) + return 0; + + return dev_err_probe(dev, ret, "Failed to lookup pinctrl wakeup state\n"); + } + + return 0; +} + struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, int sizeof_priv) { @@ -2374,9 +2463,12 @@ struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, sizeof(mram_config_vals) / 4); if (ret) { dev_err(dev, "Could not get Message RAM configuration."); - goto out; + return ERR_PTR(ret); } + if (dev->of_node && of_property_read_bool(dev->of_node, "wakeup-source")) + device_set_wakeup_capable(dev, true); + /* Get TX FIFO size * Defines the total amount of echo buffers for loopback */ @@ -2386,7 +2478,7 @@ struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, net_dev = alloc_candev(sizeof_priv, tx_fifo_size); if (!net_dev) { dev_err(dev, "Failed to allocate CAN device"); - goto out; + return ERR_PTR(-ENOMEM); } class_dev = netdev_priv(net_dev); @@ -2396,8 +2488,16 @@ struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, m_can_of_parse_mram(class_dev, mram_config_vals); spin_lock_init(&class_dev->tx_handling_spinlock); -out: + + ret = m_can_class_parse_pinctrl(class_dev); + if (ret) + goto err_free_candev; + return class_dev; + +err_free_candev: + free_candev(net_dev); + return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(m_can_class_allocate_dev); @@ -2526,7 +2626,8 @@ int m_can_class_suspend(struct device *dev) cdev->can.state = CAN_STATE_SLEEPING; } - pinctrl_pm_select_sleep_state(dev); + if (!m_can_class_wakeup_pinctrl_enabled(cdev)) + pinctrl_pm_select_sleep_state(dev); return ret; } @@ -2538,7 +2639,8 @@ int m_can_class_resume(struct device *dev) struct net_device *ndev = cdev->net; int ret = 0; - pinctrl_pm_select_default_state(dev); + if (!m_can_class_wakeup_pinctrl_enabled(cdev)) + pinctrl_pm_select_default_state(dev); if (netif_running(ndev)) { ret = m_can_clk_start(cdev); diff --git a/drivers/net/can/m_can/m_can.h b/drivers/net/can/m_can/m_can.h index 7b7600697c6b..f2f89687bbd2 100644 --- a/drivers/net/can/m_can/m_can.h +++ b/drivers/net/can/m_can/m_can.h @@ -129,6 +129,9 @@ struct m_can_classdev { struct mram_cfg mcfg[MRAM_CFG_NUM]; struct hrtimer hrtimer; + + struct pinctrl *pinctrl; + struct pinctrl_state *pinctrl_state_wakeup; }; struct m_can_classdev *m_can_class_allocate_dev(struct device *dev, int sizeof_priv); diff --git a/drivers/net/can/m_can/m_can_pci.c b/drivers/net/can/m_can/m_can_pci.c index 9ad7419f88f8..eb31ed1f9644 100644 --- a/drivers/net/can/m_can/m_can_pci.c +++ b/drivers/net/can/m_can/m_can_pci.c @@ -111,8 +111,8 @@ static int m_can_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) mcan_class = m_can_class_allocate_dev(&pci->dev, sizeof(struct m_can_pci_priv)); - if (!mcan_class) - return -ENOMEM; + if (IS_ERR(mcan_class)) + return PTR_ERR(mcan_class); priv = cdev_to_priv(mcan_class); diff --git a/drivers/net/can/m_can/m_can_platform.c b/drivers/net/can/m_can/m_can_platform.c index 4a412add2b8d..56da411878af 100644 --- a/drivers/net/can/m_can/m_can_platform.c +++ b/drivers/net/can/m_can/m_can_platform.c @@ -87,8 +87,8 @@ static int m_can_plat_probe(struct platform_device *pdev) mcan_class = m_can_class_allocate_dev(&pdev->dev, sizeof(struct m_can_plat_priv)); - if (!mcan_class) - return -ENOMEM; + if (IS_ERR(mcan_class)) + return PTR_ERR(mcan_class); priv = cdev_to_priv(mcan_class); diff --git a/drivers/net/can/m_can/tcan4x5x-core.c b/drivers/net/can/m_can/tcan4x5x-core.c index 39b0b5277b11..31cc9d0abd45 100644 --- a/drivers/net/can/m_can/tcan4x5x-core.c +++ b/drivers/net/can/m_can/tcan4x5x-core.c @@ -416,8 +416,8 @@ static int tcan4x5x_can_probe(struct spi_device *spi) mcan_class = m_can_class_allocate_dev(&spi->dev, sizeof(struct tcan4x5x_priv)); - if (!mcan_class) - return -ENOMEM; + if (IS_ERR(mcan_class)) + return PTR_ERR(mcan_class); ret = m_can_check_mram_cfg(mcan_class, TCAN4X5X_MRAM_SIZE); if (ret) |
