diff --git a/Documentation/devicetree/bindings/net/can/bosch,m_can.yaml b/Documentation/devicetree/bindings/net/can/bosch,m_can.yaml index 61ef60d8f1c7..2c9d37975bed 100644 --- a/Documentation/devicetree/bindings/net/can/bosch,m_can.yaml +++ b/Documentation/devicetree/bindings/net/can/bosch,m_can.yaml @@ -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 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)