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>
This commit is contained in:
Marc Kleine-Budde 2025-10-17 11:02:57 +02:00
commit 578dbbb952
6 changed files with 141 additions and 11 deletions

View File

@ -109,6 +109,26 @@ properties:
maximum: 32
minItems: 1
pinctrl-0:
description: Default pinctrl state
pinctrl-1:
description: Can be "sleep" or "wakeup" pinctrl state
pinctrl-2:
description: Can be "sleep" or "wakeup" pinctrl state
pinctrl-names:
description:
When present should contain at least "default" describing the default pin
states. Other states are "sleep" which describes the pinstate when
sleeping and "wakeup" describing the pins if wakeup is enabled.
minItems: 1
items:
- const: default
- enum: [ sleep, wakeup ]
- const: wakeup
power-domains:
description:
Power domain provider node and an args specifier containing
@ -125,6 +145,11 @@ properties:
minItems: 1
maxItems: 2
wakeup-source:
$ref: /schemas/types.yaml#/definitions/phandle-array
description:
List of phandles to system idle states in which mcan can wakeup the system.
required:
- compatible
- reg

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)