From 2290aefa2e90a43af8555ad6431d49de43259aa3 Mon Sep 17 00:00:00 2001 From: Franklin S Cooper Jr Date: Wed, 10 Jan 2018 16:25:18 +0530 Subject: can: dev: Add support for limiting configured bitrate Various CAN or CAN-FD IP may be able to run at a faster rate than what the transceiver the CAN node is connected to. This can lead to unexpected errors. However, CAN transceivers typically have fixed limitations and provide no means to discover these limitations at runtime. Therefore, add support for a can-transceiver node that can be reused by other CAN peripheral drivers to determine for both CAN and CAN-FD what the max bitrate that can be used. If the user tries to configure CAN to pass these maximum bitrates it will throw an error. Also add support for reading bitrate_max via the netlink interface. Reviewed-by: Suman Anna Signed-off-by: Franklin S Cooper Jr [nsekhar@ti.com: fix build error with !CONFIG_OF] Signed-off-by: Sekhar Nori Signed-off-by: Faiz Abbas Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/can/dev.c b/drivers/net/can/dev.c index 365a8cc62405..cc94604b23e0 100644 --- a/drivers/net/can/dev.c +++ b/drivers/net/can/dev.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #define MOD_DESC "CAN device driver interface" @@ -814,6 +815,29 @@ int open_candev(struct net_device *dev) } EXPORT_SYMBOL_GPL(open_candev); +#ifdef CONFIG_OF +/* Common function that can be used to understand the limitation of + * a transceiver when it provides no means to determine these limitations + * at runtime. + */ +void of_can_transceiver(struct net_device *dev) +{ + struct device_node *dn; + struct can_priv *priv = netdev_priv(dev); + struct device_node *np = dev->dev.parent->of_node; + int ret; + + dn = of_get_child_by_name(np, "can-transceiver"); + if (!dn) + return; + + ret = of_property_read_u32(dn, "max-bitrate", &priv->bitrate_max); + if ((ret && ret != -EINVAL) || (!ret && !priv->bitrate_max)) + netdev_warn(dev, "Invalid value for transceiver max bitrate. Ignoring bitrate limit.\n"); +} +EXPORT_SYMBOL_GPL(of_can_transceiver); +#endif + /* * Common close function for cleanup before the device gets closed. * @@ -913,6 +937,13 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[], priv->bitrate_const_cnt); if (err) return err; + + if (priv->bitrate_max && bt.bitrate > priv->bitrate_max) { + netdev_err(dev, "arbitration bitrate surpasses transceiver capabilities of %d bps\n", + priv->bitrate_max); + return -EINVAL; + } + memcpy(&priv->bittiming, &bt, sizeof(bt)); if (priv->do_set_bittiming) { @@ -997,6 +1028,13 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[], priv->data_bitrate_const_cnt); if (err) return err; + + if (priv->bitrate_max && dbt.bitrate > priv->bitrate_max) { + netdev_err(dev, "canfd data bitrate surpasses transceiver capabilities of %d bps\n", + priv->bitrate_max); + return -EINVAL; + } + memcpy(&priv->data_bittiming, &dbt, sizeof(dbt)); if (priv->do_set_data_bittiming) { @@ -1064,6 +1102,7 @@ static size_t can_get_size(const struct net_device *dev) if (priv->data_bitrate_const) /* IFLA_CAN_DATA_BITRATE_CONST */ size += nla_total_size(sizeof(*priv->data_bitrate_const) * priv->data_bitrate_const_cnt); + size += sizeof(priv->bitrate_max); /* IFLA_CAN_BITRATE_MAX */ return size; } @@ -1121,7 +1160,11 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev) nla_put(skb, IFLA_CAN_DATA_BITRATE_CONST, sizeof(*priv->data_bitrate_const) * priv->data_bitrate_const_cnt, - priv->data_bitrate_const)) + priv->data_bitrate_const)) || + + (nla_put(skb, IFLA_CAN_BITRATE_MAX, + sizeof(priv->bitrate_max), + &priv->bitrate_max)) ) return -EMSGSIZE; -- cgit v1.2.3 From e759c626d826649cf393da34cd59656d08afb52d Mon Sep 17 00:00:00 2001 From: Franklin S Cooper Jr Date: Tue, 16 Jan 2018 17:07:14 +0530 Subject: can: m_can: Support higher speed CAN-FD bitrates During test transmitting using CAN-FD at high bitrates (> 2 Mbps) would fail. Scoping the signals I noticed that only a single bit was being transmitted and with a bit more investigation realized the actual MCAN IP would go back to initialization mode automatically. It appears this issue is due to the MCAN needing to use the Transmitter Delay Compensation Mode with the correct value for the transmitter delay compensation offset (tdco). What impacts the tdco value isn't 100% clear but to calculate it you use an equation defined in the MCAN User's Guide. The user guide mentions that this register needs to be set based on clock values, secondary sample point and the data bitrate. One of the key variables that can't automatically be determined is the secondary sample point (ssp). This ssp is similar to the sp but is specific to this transmitter delay compensation mode. The guidelines for configuring ssp is rather vague but via some CAN test it appears for DRA76x that putting the value same as data sampling point works. The CAN-CIA's "Bit Time Requirements for CAN FD" paper presented at the International CAN Conference 2013 indicates that this TDC mode is only needed for data bit rates above 2.5 Mbps. Therefore, only enable this mode when the data bit rate is above 2.5 Mbps. Signed-off-by: Franklin S Cooper Jr Signed-off-by: Sekhar Nori Signed-off-by: Faiz Abbas Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 46 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index f4947a74b65f..79dd3c36ea32 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -126,6 +126,12 @@ enum m_can_mram_cfg { #define DBTP_DSJW_SHIFT 0 #define DBTP_DSJW_MASK (0xf << DBTP_DSJW_SHIFT) +/* Transmitter Delay Compensation Register (TDCR) */ +#define TDCR_TDCO_SHIFT 8 +#define TDCR_TDCO_MASK (0x7F << TDCR_TDCO_SHIFT) +#define TDCR_TDCF_SHIFT 0 +#define TDCR_TDCF_MASK (0x7F << TDCR_TDCF_SHIFT) + /* Test Register (TEST) */ #define TEST_LBCK BIT(4) @@ -987,13 +993,47 @@ static int m_can_set_bittiming(struct net_device *dev) m_can_write(priv, M_CAN_NBTP, reg_btp); if (priv->can.ctrlmode & CAN_CTRLMODE_FD) { + reg_btp = 0; brp = dbt->brp - 1; sjw = dbt->sjw - 1; tseg1 = dbt->prop_seg + dbt->phase_seg1 - 1; tseg2 = dbt->phase_seg2 - 1; - reg_btp = (brp << DBTP_DBRP_SHIFT) | (sjw << DBTP_DSJW_SHIFT) | - (tseg1 << DBTP_DTSEG1_SHIFT) | - (tseg2 << DBTP_DTSEG2_SHIFT); + + /* TDC is only needed for bitrates beyond 2.5 MBit/s. + * This is mentioned in the "Bit Time Requirements for CAN FD" + * paper presented at the International CAN Conference 2013 + */ + if (dbt->bitrate > 2500000) { + u32 tdco, ssp; + + /* Use the same value of secondary sampling point + * as the data sampling point + */ + ssp = dbt->sample_point; + + /* Equation based on Bosch's M_CAN User Manual's + * Transmitter Delay Compensation Section + */ + tdco = (priv->can.clock.freq / 1000) * + ssp / dbt->bitrate; + + /* Max valid TDCO value is 127 */ + if (tdco > 127) { + netdev_warn(dev, "TDCO value of %u is beyond maximum. Using maximum possible value\n", + tdco); + tdco = 127; + } + + reg_btp |= DBTP_TDC; + m_can_write(priv, M_CAN_TDCR, + tdco << TDCR_TDCO_SHIFT); + } + + reg_btp |= (brp << DBTP_DBRP_SHIFT) | + (sjw << DBTP_DSJW_SHIFT) | + (tseg1 << DBTP_DTSEG1_SHIFT) | + (tseg2 << DBTP_DTSEG2_SHIFT); + m_can_write(priv, M_CAN_DBTP, reg_btp); } -- cgit v1.2.3 From b25abca6aa4fd2fcdafc214d489ccd57df67e88e Mon Sep 17 00:00:00 2001 From: Faiz Abbas Date: Wed, 10 Jan 2018 16:25:20 +0530 Subject: can: m_can: Remove check for version when allocating m_can net device Currently the m_can version is used to set the tx_fifo_count to 1 when allocating the net device. However, this is redundant as a value of 1 for the tx_fifo_count needs to be provided in the bosch,mram-cfg property of the device tree node anyway. Therefore, remove check for version when allocating the net device. Signed-off-by: Faiz Abbas Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index 79dd3c36ea32..4f7de0b07c9b 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -1253,7 +1253,6 @@ static struct net_device *alloc_m_can_dev(struct platform_device *pdev, struct net_device *dev; struct m_can_priv *priv; int m_can_version; - unsigned int echo_buffer_count; m_can_version = m_can_check_core_release(addr); /* return if unsupported version */ @@ -1262,12 +1261,7 @@ static struct net_device *alloc_m_can_dev(struct platform_device *pdev, goto return_dev; } - /* If version < 3.1.x, then only one echo buffer is used */ - echo_buffer_count = ((m_can_version == 30) - ? 1U - : (unsigned int)tx_fifo_size); - - dev = alloc_candev(sizeof(*priv), echo_buffer_count); + dev = alloc_candev(sizeof(*priv), tx_fifo_size); if (!dev) { dev = NULL; goto return_dev; -- cgit v1.2.3 From 5e520edd91f0cd52f8f9f855bc2c1558e85d30da Mon Sep 17 00:00:00 2001 From: Faiz Abbas Date: Tue, 16 Jan 2018 17:07:12 +0530 Subject: can: m_can: Move allocation of net device to probe With the version no longer required to allocate the net device, it can be moved to probe and the alloc_m_can_dev() function can be simplified. Therefore, move the allocation of net device to probe and change alloc_m_can_dev() to setup_m_can_dev(). Signed-off-by: Faiz Abbas Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index 4f7de0b07c9b..6c2011bbafb3 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -1247,25 +1247,20 @@ static bool m_can_niso_supported(const struct m_can_priv *priv) return !niso_timeout; } -static struct net_device *alloc_m_can_dev(struct platform_device *pdev, - void __iomem *addr, u32 tx_fifo_size) +static int m_can_dev_setup(struct platform_device *pdev, struct net_device *dev, + void __iomem *addr) { - struct net_device *dev; struct m_can_priv *priv; int m_can_version; m_can_version = m_can_check_core_release(addr); /* return if unsupported version */ if (!m_can_version) { - dev = NULL; - goto return_dev; + dev_err(&pdev->dev, "Unsupported version number: %2d", + m_can_version); + return -EINVAL; } - dev = alloc_candev(sizeof(*priv), tx_fifo_size); - if (!dev) { - dev = NULL; - goto return_dev; - } priv = netdev_priv(dev); netif_napi_add(dev, &priv->napi, m_can_poll, M_CAN_NAPI_WEIGHT); @@ -1307,16 +1302,12 @@ static struct net_device *alloc_m_can_dev(struct platform_device *pdev, : 0); break; default: - /* Unsupported device: free candev */ - free_m_can_dev(dev); dev_err(&pdev->dev, "Unsupported version number: %2d", priv->version); - dev = NULL; - break; + return -EINVAL; } -return_dev: - return dev; + return 0; } static int m_can_open(struct net_device *dev) @@ -1656,11 +1647,16 @@ static int m_can_plat_probe(struct platform_device *pdev) tx_fifo_size = mram_config_vals[7]; /* allocate the m_can device */ - dev = alloc_m_can_dev(pdev, addr, tx_fifo_size); + dev = alloc_candev(sizeof(*priv), tx_fifo_size); if (!dev) { ret = -ENOMEM; goto disable_cclk_ret; } + + ret = m_can_dev_setup(pdev, dev, addr); + if (ret) + goto failed_free_dev; + priv = netdev_priv(dev); dev->irq = irq; priv->device = &pdev->dev; -- cgit v1.2.3 From 63dffc1c699d2b808e7d192ffd67d443be136a56 Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Tue, 16 Jan 2018 13:58:53 +0100 Subject: can: m_can: get rid of function free_m_can_dev() As the previous patch removed alloc_m_can_dev(), let's get rid of the corresponding free_m_can_dev() and call free_candev() directly. Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index 6c2011bbafb3..f882cd5df4fa 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -1183,11 +1183,6 @@ static int m_can_set_mode(struct net_device *dev, enum can_mode mode) return 0; } -static void free_m_can_dev(struct net_device *dev) -{ - free_candev(dev); -} - /* Checks core release number of M_CAN * returns 0 if an unsupported device is detected * else it returns the release and step coded as: @@ -1689,7 +1684,7 @@ static int m_can_plat_probe(struct platform_device *pdev) goto disable_cclk_ret; failed_free_dev: - free_m_can_dev(dev); + free_candev(dev); disable_cclk_ret: clk_disable_unprepare(cclk); disable_hclk_ret: @@ -1753,7 +1748,7 @@ static int m_can_plat_remove(struct platform_device *pdev) unregister_m_can_dev(dev); platform_set_drvdata(pdev, NULL); - free_m_can_dev(dev); + free_candev(dev); return 0; } -- cgit v1.2.3 From cdf8259d65739a04871513d4d17bb342ba1d4a46 Mon Sep 17 00:00:00 2001 From: Faiz Abbas Date: Tue, 16 Jan 2018 17:07:13 +0530 Subject: can: m_can: Add PM Support Add support for CONFIG_PM which is the new way to handle managing clocks. Move the clock management to pm_runtime_resume() and pm_runtime_suspend() callbacks for the driver. CONFIG_PM is required by OMAP based devices to handle clock management. Therefore, this allows future Texas Instruments SoCs that have the MCAN IP to work with this driver. Signed-off-by: Faiz Abbas Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 100 ++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 38 deletions(-) (limited to 'drivers') diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index f882cd5df4fa..2f0e56d4c332 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -631,21 +632,16 @@ static int m_can_clk_start(struct m_can_priv *priv) { int err; - err = clk_prepare_enable(priv->hclk); + err = pm_runtime_get_sync(priv->device); if (err) - return err; - - err = clk_prepare_enable(priv->cclk); - if (err) - clk_disable_unprepare(priv->hclk); + pm_runtime_put_noidle(priv->device); return err; } static void m_can_clk_stop(struct m_can_priv *priv) { - clk_disable_unprepare(priv->cclk); - clk_disable_unprepare(priv->hclk); + pm_runtime_put_sync(priv->device); } static int m_can_get_berr_counter(const struct net_device *dev, @@ -1594,37 +1590,26 @@ static int m_can_plat_probe(struct platform_device *pdev) goto failed_ret; } - /* Enable clocks. Necessary to read Core Release in order to determine - * M_CAN version - */ - ret = clk_prepare_enable(hclk); - if (ret) - goto disable_hclk_ret; - - ret = clk_prepare_enable(cclk); - if (ret) - goto disable_cclk_ret; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "m_can"); addr = devm_ioremap_resource(&pdev->dev, res); irq = platform_get_irq_byname(pdev, "int0"); if (IS_ERR(addr) || irq < 0) { ret = -EINVAL; - goto disable_cclk_ret; + goto failed_ret; } /* message ram could be shared */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "message_ram"); if (!res) { ret = -ENODEV; - goto disable_cclk_ret; + goto failed_ret; } mram_addr = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!mram_addr) { ret = -ENOMEM; - goto disable_cclk_ret; + goto failed_ret; } /* get message ram configuration */ @@ -1633,7 +1618,7 @@ static int m_can_plat_probe(struct platform_device *pdev) sizeof(mram_config_vals) / 4); if (ret) { dev_err(&pdev->dev, "Could not get Message RAM configuration."); - goto disable_cclk_ret; + goto failed_ret; } /* Get TX FIFO size @@ -1645,13 +1630,9 @@ static int m_can_plat_probe(struct platform_device *pdev) dev = alloc_candev(sizeof(*priv), tx_fifo_size); if (!dev) { ret = -ENOMEM; - goto disable_cclk_ret; + goto failed_ret; } - ret = m_can_dev_setup(pdev, dev, addr); - if (ret) - goto failed_free_dev; - priv = netdev_priv(dev); dev->irq = irq; priv->device = &pdev->dev; @@ -1665,11 +1646,23 @@ static int m_can_plat_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dev); SET_NETDEV_DEV(dev, &pdev->dev); + /* Enable clocks. Necessary to read Core Release in order to determine + * M_CAN version + */ + pm_runtime_enable(&pdev->dev); + ret = m_can_clk_start(priv); + if (ret) + goto pm_runtime_fail; + + ret = m_can_dev_setup(pdev, dev, addr); + if (ret) + goto clk_disable; + ret = register_m_can_dev(dev); if (ret) { dev_err(&pdev->dev, "registering %s failed (err=%d)\n", KBUILD_MODNAME, ret); - goto failed_free_dev; + goto clk_disable; } devm_can_led_init(dev); @@ -1680,15 +1673,13 @@ static int m_can_plat_probe(struct platform_device *pdev) /* Probe finished * Stop clocks. They will be reactivated once the M_CAN device is opened */ - - goto disable_cclk_ret; - -failed_free_dev: - free_candev(dev); -disable_cclk_ret: - clk_disable_unprepare(cclk); -disable_hclk_ret: - clk_disable_unprepare(hclk); +clk_disable: + m_can_clk_stop(priv); +pm_runtime_fail: + if (ret) { + pm_runtime_disable(&pdev->dev); + free_candev(dev); + } failed_ret: return ret; } @@ -1746,6 +1737,9 @@ static int m_can_plat_remove(struct platform_device *pdev) struct net_device *dev = platform_get_drvdata(pdev); unregister_m_can_dev(dev); + + pm_runtime_disable(&pdev->dev); + platform_set_drvdata(pdev, NULL); free_candev(dev); @@ -1753,7 +1747,37 @@ static int m_can_plat_remove(struct platform_device *pdev) return 0; } +static int m_can_runtime_suspend(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct m_can_priv *priv = netdev_priv(ndev); + + clk_disable_unprepare(priv->cclk); + clk_disable_unprepare(priv->hclk); + + return 0; +} + +static int m_can_runtime_resume(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct m_can_priv *priv = netdev_priv(ndev); + int err; + + err = clk_prepare_enable(priv->hclk); + if (err) + return err; + + err = clk_prepare_enable(priv->cclk); + if (err) + clk_disable_unprepare(priv->hclk); + + return err; +} + static const struct dev_pm_ops m_can_pmops = { + SET_RUNTIME_PM_OPS(m_can_runtime_suspend, + m_can_runtime_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(m_can_suspend, m_can_resume) }; -- cgit v1.2.3 From 31643dc8fc66abdd3f68800483df4a5420f5e69c Mon Sep 17 00:00:00 2001 From: Franklin S Cooper Jr Date: Wed, 10 Jan 2018 16:25:19 +0530 Subject: can: m_can: Add call to of_can_transceiver Add call to new generic functions that provides support via a binding to limit the arbitration rate and/or data rate imposed by the physical transceiver connected to the MCAN peripheral. Signed-off-by: Franklin S Cooper Jr Signed-off-by: Sekhar Nori Signed-off-by: Faiz Abbas Signed-off-by: Marc Kleine-Budde --- drivers/net/can/m_can/m_can.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c index 2f0e56d4c332..49ed8737871f 100644 --- a/drivers/net/can/m_can/m_can.c +++ b/drivers/net/can/m_can/m_can.c @@ -1667,6 +1667,8 @@ static int m_can_plat_probe(struct platform_device *pdev) devm_can_led_init(dev); + of_can_transceiver(dev); + dev_info(&pdev->dev, "%s device registered (irq=%d, version=%d)\n", KBUILD_MODNAME, dev->irq, priv->version); -- cgit v1.2.3