Hello Markus, do you have time to review this patch? I want to send a PR tomorrow. regards, Marc On 01.05.2024 14:42:03, Martin Hundebøll wrote: > The m_can driver sets and clears the CCCR.INIT bit during probe (both > when testing the NON-ISO bit, and when configuring the chip). After > clearing the CCCR.INIT bit, the transceiver enters normal mode, where it > affects the CAN bus (i.e. it ACKs frames). This can cause troubles when > the m_can node is only used for monitoring the bus, as one cannot setup > listen-only mode before the device is probed. > > Rework the probe flow, so that the CCCR.INIT bit is only cleared when > upping the device. First, the tcan4x5x driver is changed to stay in > standby mode during/after probe. This in turn requires changes when > setting bits in the CCCR register, as its CSR and CSA bits are always > high in standby mode. > > Signed-off-by: Martin Hundebøll <martin@xxxxxxxxxx> > --- > > Changes since v1: > * Implement Markus review comments: > - Rename m_can_cccr_wait_bits() to m_can_cccr_update_bits() > - Explicitly set CCCR_INIT bit in m_can_dev_setup() > - Revert to 5 timeouts/tries to 10 > - Use m_can_config_{en|dis}able() in m_can_niso_supported() > - Revert move of call to m_can_enable_all_interrupts() > - Return -EBUSY on failure to enter normal mode > - Use tcan4x5x_clear_interrupts() in tcan4x5x_can_probe() > > drivers/net/can/m_can/m_can.c | 131 +++++++++++++++----------- > drivers/net/can/m_can/tcan4x5x-core.c | 13 ++- > 2 files changed, 85 insertions(+), 59 deletions(-) > > diff --git a/drivers/net/can/m_can/m_can.c b/drivers/net/can/m_can/m_can.c > index 14b231c4d7ec..7974aaa5d8cc 100644 > --- a/drivers/net/can/m_can/m_can.c > +++ b/drivers/net/can/m_can/m_can.c > @@ -379,38 +379,60 @@ m_can_txe_fifo_read(struct m_can_classdev *cdev, u32 fgi, u32 offset, u32 *val) > return cdev->ops->read_fifo(cdev, addr_offset, val, 1); > } > > -static void m_can_config_endisable(struct m_can_classdev *cdev, bool enable) > +static bool m_can_cccr_update_bits(struct m_can_classdev *cdev, u32 mask, u32 val) > { > - u32 cccr = m_can_read(cdev, M_CAN_CCCR); > - u32 timeout = 10; > - u32 val = 0; > - > - /* Clear the Clock stop request if it was set */ > - if (cccr & CCCR_CSR) > - cccr &= ~CCCR_CSR; > - > - if (enable) { > - /* enable m_can configuration */ > - m_can_write(cdev, M_CAN_CCCR, cccr | CCCR_INIT); > - udelay(5); > - /* CCCR.CCE can only be set/reset while CCCR.INIT = '1' */ > - m_can_write(cdev, M_CAN_CCCR, cccr | CCCR_INIT | CCCR_CCE); > - } else { > - m_can_write(cdev, M_CAN_CCCR, cccr & ~(CCCR_INIT | CCCR_CCE)); > - } > + u32 val_before = m_can_read(cdev, M_CAN_CCCR); > + u32 val_after = (val_before & ~mask) | val; > + size_t tries = 10; > + > + if (!(mask & CCCR_INIT) && !(val_before & CCCR_INIT)) > + dev_warn(cdev->dev, > + "trying to configure device when in normal mode. Expect failures\n"); > + > + /* The chip should be in standby mode when changing the CCCR register, > + * and some chips set the CSR and CSA bits when in standby. Furthermore, > + * the CSR and CSA bits should be written as zeros, even when they read > + * ones. > + */ > + val_after &= ~(CCCR_CSR | CCCR_CSA); > + > + while (tries--) { > + u32 val_read; > + > + /* Write the desired value in each try, as setting some bits in > + * the CCCR register require other bits to be set first. E.g. > + * setting the NISO bit requires setting the CCE bit first. > + */ > + m_can_write(cdev, M_CAN_CCCR, val_after); > + > + val_read = m_can_read(cdev, M_CAN_CCCR) & ~(CCCR_CSR | CCCR_CSA); > > - /* there's a delay for module initialization */ > - if (enable) > - val = CCCR_INIT | CCCR_CCE; > - > - while ((m_can_read(cdev, M_CAN_CCCR) & (CCCR_INIT | CCCR_CCE)) != val) { > - if (timeout == 0) { > - netdev_warn(cdev->net, "Failed to init module\n"); > - return; > - } > - timeout--; > - udelay(1); > + if (val_read == val_after) > + return true; > + > + usleep_range(1, 5); > } > + > + return false; > +} > + > +static void m_can_config_enable(struct m_can_classdev *cdev) > +{ > + /* CCCR_INIT must be set in order to set CCCR_CCE, but access to > + * configuration registers should only be enabled when in standby mode, > + * where CCCR_INIT is always set. > + */ > + if (!m_can_cccr_update_bits(cdev, CCCR_CCE, CCCR_CCE)) > + netdev_err(cdev->net, "failed to enable configuration mode\n"); > +} > + > +static void m_can_config_disable(struct m_can_classdev *cdev) > +{ > + /* Only clear CCCR_CCE, since CCCR_INIT cannot be cleared while in > + * standby mode > + */ > + if (!m_can_cccr_update_bits(cdev, CCCR_CCE, 0)) > + netdev_err(cdev->net, "failed to disable configuration registers\n"); > } > > static void m_can_interrupt_enable(struct m_can_classdev *cdev, u32 interrupts) > @@ -1403,7 +1425,7 @@ static int m_can_chip_config(struct net_device *dev) > interrupts &= ~(IR_ARA | IR_ELO | IR_DRX | IR_TEFF | IR_TFE | IR_TCF | > IR_HPM | IR_RF1F | IR_RF1W | IR_RF1N | IR_RF0F); > > - m_can_config_endisable(cdev, true); > + m_can_config_enable(cdev); > > /* RX Buffer/FIFO Element Size 64 bytes data field */ > m_can_write(cdev, M_CAN_RXESC, > @@ -1521,7 +1543,7 @@ static int m_can_chip_config(struct net_device *dev) > FIELD_PREP(TSCC_TCP_MASK, 0xf) | > FIELD_PREP(TSCC_TSS_MASK, TSCC_TSS_INTERNAL)); > > - m_can_config_endisable(cdev, false); > + m_can_config_disable(cdev); > > if (cdev->ops->init) > cdev->ops->init(cdev); > @@ -1550,6 +1572,11 @@ static int m_can_start(struct net_device *dev) > cdev->tx_fifo_putidx = FIELD_GET(TXFQS_TFQPI_MASK, > m_can_read(cdev, M_CAN_TXFQS)); > > + if (!m_can_cccr_update_bits(cdev, CCCR_INIT, 0)) { > + netdev_err(dev, "failed to enter normal mode\n"); > + return -EBUSY; > + } > + > return 0; > } > > @@ -1603,33 +1630,20 @@ static int m_can_check_core_release(struct m_can_classdev *cdev) > */ > static bool m_can_niso_supported(struct m_can_classdev *cdev) > { > - u32 cccr_reg, cccr_poll = 0; > - int niso_timeout = -ETIMEDOUT; > - int i; > + bool niso_supported; > > - m_can_config_endisable(cdev, true); > - cccr_reg = m_can_read(cdev, M_CAN_CCCR); > - cccr_reg |= CCCR_NISO; > - m_can_write(cdev, M_CAN_CCCR, cccr_reg); > + m_can_config_enable(cdev); > > - for (i = 0; i <= 10; i++) { > - cccr_poll = m_can_read(cdev, M_CAN_CCCR); > - if (cccr_poll == cccr_reg) { > - niso_timeout = 0; > - break; > - } > + /* First try to set the NISO bit. */ > + niso_supported = m_can_cccr_update_bits(cdev, CCCR_NISO, CCCR_NISO); > > - usleep_range(1, 5); > - } > + /* Then clear the it again. */ > + if (!m_can_cccr_update_bits(cdev, CCCR_NISO, 0)) > + dev_err(cdev->dev, "failed to revert the NON-ISO bit in CCCR\n"); > > - /* Clear NISO */ > - cccr_reg &= ~(CCCR_NISO); > - m_can_write(cdev, M_CAN_CCCR, cccr_reg); > + m_can_config_disable(cdev); > > - m_can_config_endisable(cdev, false); > - > - /* return false if time out (-ETIMEDOUT), else return true */ > - return !niso_timeout; > + return niso_supported; > } > > static int m_can_dev_setup(struct m_can_classdev *cdev) > @@ -1694,8 +1708,12 @@ static int m_can_dev_setup(struct m_can_classdev *cdev) > return -EINVAL; > } > > - if (cdev->ops->init) > - cdev->ops->init(cdev); > + /* Forcing standby mode should be redunant, as the chip should be in > + * standby after a reset. Write the INIT bit anyways, should the chip > + * be configured by previous stage. > + */ > + if (!m_can_cccr_update_bits(cdev, CCCR_INIT, CCCR_INIT)) > + return -EBUSY; > > return 0; > } > @@ -1708,7 +1726,8 @@ static void m_can_stop(struct net_device *dev) > m_can_disable_all_interrupts(cdev); > > /* Set init mode to disengage from the network */ > - m_can_config_endisable(cdev, true); > + if (!m_can_cccr_update_bits(cdev, CCCR_INIT, CCCR_INIT)) > + netdev_err(dev, "failed to enter standby mode\n"); > > /* set the state as STOPPED */ > cdev->can.state = CAN_STATE_STOPPED; > diff --git a/drivers/net/can/m_can/tcan4x5x-core.c b/drivers/net/can/m_can/tcan4x5x-core.c > index a42600dac70d..d723206ac7c9 100644 > --- a/drivers/net/can/m_can/tcan4x5x-core.c > +++ b/drivers/net/can/m_can/tcan4x5x-core.c > @@ -453,10 +453,17 @@ static int tcan4x5x_can_probe(struct spi_device *spi) > goto out_power; > } > > - ret = tcan4x5x_init(mcan_class); > + tcan4x5x_check_wake(priv); > + > + ret = tcan4x5x_write_tcan_reg(mcan_class, TCAN4X5X_INT_EN, 0); > if (ret) { > - dev_err(&spi->dev, "tcan initialization failed %pe\n", > - ERR_PTR(ret)); > + dev_err(&spi->dev, "Disabling interrupts failed %pe\n", ERR_PTR(ret)); > + goto out_power; > + } > + > + ret = tcan4x5x_clear_interrupts(mcan_class); > + if (ret) { > + dev_err(&spi->dev, "Clearing interrupts failed %pe\n", ERR_PTR(ret)); > goto out_power; > } > > -- > 2.44.0 > > > -- Pengutronix e.K. | Marc Kleine-Budde | Embedded Linux | https://www.pengutronix.de | Vertretung Nürnberg | Phone: +49-5121-206917-129 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
Attachment:
signature.asc
Description: PGP signature