Re: [RFC PATCH net-next 2/6] net: ethernet: add mac-phy interrupt support with reset complete handling

From: Ziyang Xuan (William)
Date: Mon Sep 11 2023 - 17:10:17 EST


> Register MAC-PHY interrupt and handle reset complete interrupt. Reset
> complete bit is set when the MAC-PHY reset complete and ready for
> configuration. When it is set, it will generate a non-maskable interrupt
> to alert the SPI host. Additionally reset complete bit in the STS0
> register has to be written by one upon reset complete to clear the
> interrupt.
>
> Signed-off-by: Parthiban Veerasooran <Parthiban.Veerasooran@xxxxxxxxxxxxx>
> ---
> drivers/net/ethernet/oa_tc6.c | 141 ++++++++++++++++++++++++++++++++--
> include/linux/oa_tc6.h | 16 +++-
> 2 files changed, 150 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
> index 613cf034430a..0019f70345b6 100644
> --- a/drivers/net/ethernet/oa_tc6.c
> +++ b/drivers/net/ethernet/oa_tc6.c
> @@ -6,6 +6,7 @@
> */
>
> #include <linux/bitfield.h>
> +#include <linux/interrupt.h>
> #include <linux/oa_tc6.h>
>
> static int oa_tc6_spi_transfer(struct spi_device *spi, u8 *ptx, u8 *prx,
> @@ -160,10 +161,16 @@ int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 addr, u32 val[], u8 len,
> if (ret)
> goto err_ctrl;
>
> - /* Check echoed/received control reply */
> - ret = oa_tc6_check_control(tc6, tx_buf, rx_buf, len, wnr, ctrl_prot);
> - if (ret)
> - goto err_ctrl;
> + /* In case of reset write, the echoed control command doesn't have any
> + * valid data. So no need to check for error.
> + */
> + if (addr != OA_TC6_RESET) {
> + /* Check echoed/received control reply */
> + ret = oa_tc6_check_control(tc6, tx_buf, rx_buf, len, wnr,
> + ctrl_prot);
> + if (ret)
> + goto err_ctrl;
> + }
>
> if (!wnr) {
> /* Copy read data from the rx data in case of ctrl read */
> @@ -186,6 +193,88 @@ int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 addr, u32 val[], u8 len,
> return ret;
> }
>
> +static int oa_tc6_handler(void *data)
> +{
> + struct oa_tc6 *tc6 = data;
> + u32 regval;
> + int ret;
> +
> + while (likely(!kthread_should_stop())) {
> + wait_event_interruptible(tc6->tc6_wq, tc6->int_flag ||
> + kthread_should_stop());
> + if (tc6->int_flag) {
> + tc6->int_flag = false;
> + ret = oa_tc6_perform_ctrl(tc6, OA_TC6_STS0, &regval, 1,
> + false, false);
> + if (ret) {
> + dev_err(&tc6->spi->dev, "Failed to read STS0\n");
> + continue;
> + }
> + /* Check for reset complete interrupt status */
> + if (regval & RESETC) {
> + regval = RESETC;
> + /* SPI host should write RESETC bit with one to
> + * clear the reset interrupt status.
> + */
> + ret = oa_tc6_perform_ctrl(tc6, OA_TC6_STS0,
> + &regval, 1, true,
> + false);
> + if (ret) {
> + dev_err(&tc6->spi->dev,
> + "Failed to write STS0\n");
> + continue;
> + }
> + complete(&tc6->rst_complete);
> + }
> + }
> + }
> + return 0;
> +}
> +
> +static irqreturn_t macphy_irq(int irq, void *dev_id)
> +{
> + struct oa_tc6 *tc6 = dev_id;
> +
> + /* Wake tc6 task to perform interrupt action */
> + tc6->int_flag = true;
> + wake_up_interruptible(&tc6->tc6_wq);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int oa_tc6_sw_reset(struct oa_tc6 *tc6)
> +{
> + long timeleft;
> + u32 regval;
> + int ret;
> +
> + /* Perform software reset with both protected and unprotected control
> + * commands because the driver doesn't know the current status of the
> + * MAC-PHY.
> + */
> + regval = SW_RESET;
> + reinit_completion(&tc6->rst_complete);
> + ret = oa_tc6_perform_ctrl(tc6, OA_TC6_RESET, &regval, 1, true, false);
> + if (ret) {
> + dev_err(&tc6->spi->dev, "RESET register write failed\n");
> + return ret;
> + }
> +
> + ret = oa_tc6_perform_ctrl(tc6, OA_TC6_RESET, &regval, 1, true, true);
> + if (ret) {
> + dev_err(&tc6->spi->dev, "RESET register write failed\n");
> + return ret;
> + }
> + timeleft = wait_for_completion_interruptible_timeout(&tc6->rst_complete,
> + msecs_to_jiffies(1));
> + if (timeleft <= 0) {
> + dev_err(&tc6->spi->dev, "MAC-PHY reset failed\n");
> + return -ENODEV;
> + }
> +
> + return 0;
> +}
> +
> int oa_tc6_write_register(struct oa_tc6 *tc6, u32 addr, u32 val[], u8 len)
> {
> return oa_tc6_perform_ctrl(tc6, addr, val, len, true, tc6->ctrl_prot);
> @@ -201,6 +290,7 @@ EXPORT_SYMBOL_GPL(oa_tc6_read_register);
> struct oa_tc6 *oa_tc6_init(struct spi_device *spi)
> {
> struct oa_tc6 *tc6;
> + int ret;
>
> if (!spi)
> return NULL;
> @@ -211,12 +301,51 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi)
>
> tc6->spi = spi;
>
> + /* Used for triggering the OA TC6 task */
> + init_waitqueue_head(&tc6->tc6_wq);
> +
> + init_completion(&tc6->rst_complete);
> +
> + /* This task performs the SPI transfer */
> + tc6->tc6_task = kthread_run(oa_tc6_handler, tc6, "OA TC6 Task");
> + if (IS_ERR(tc6->tc6_task))
> + goto err_tc6_task;
> +
> + /* Set the highest priority to the tc6 task as it is time critical */
> + sched_set_fifo(tc6->tc6_task);
> +
> + /* Register MAC-PHY interrupt service routine */
> + ret = devm_request_irq(&spi->dev, spi->irq, macphy_irq, 0, "macphy int",
> + tc6);
> + if ((ret != -ENOTCONN) && ret < 0) {
> + dev_err(&spi->dev, "Error attaching macphy irq %d\n", ret);
> + goto err_macphy_irq;
> + }
> +
> + /* Perform MAC-PHY software reset */
> + if (oa_tc6_sw_reset(tc6))
> + goto err_macphy_reset;
> +
> return tc6;
> +
> +err_macphy_reset:
> + devm_free_irq(&tc6->spi->dev, tc6->spi->irq, tc6);
> +err_macphy_irq:
> + kthread_stop(tc6->tc6_task);
> +err_tc6_task:
> + kfree(tc6);
> + return NULL;
> }
> EXPORT_SYMBOL_GPL(oa_tc6_init);
>
> -void oa_tc6_deinit(struct oa_tc6 *tc6)
> +int oa_tc6_deinit(struct oa_tc6 *tc6)
> {
> - kfree(tc6);
> + int ret;
> +
> + devm_free_irq(&tc6->spi->dev, tc6->spi->irq, tc6);
> + ret = kthread_stop(tc6->tc6_task);

kthread_stop() will the result of threadfn(). Here mean that if threadfn()
return non-zero, deinit() will fail. But the KTHREAD_SHOULD_STOP already be set.
And oa_tc6_handler() will end. Please check it is what you want.

> + if (!ret)
> + kfree(tc6);
> + return ret;
> }
> EXPORT_SYMBOL_GPL(oa_tc6_deinit);
> diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
> index 5e0a58ab1dcd..315f061c2dfe 100644
> --- a/include/linux/oa_tc6.h
> +++ b/include/linux/oa_tc6.h
> @@ -17,15 +17,29 @@
> #define CTRL_HDR_LEN GENMASK(7, 1) /* Length */
> #define CTRL_HDR_P BIT(0) /* Parity Bit */
>
> +/* Open Alliance TC6 Standard Control and Status Registers */
> +#define OA_TC6_RESET 0x0003 /* Reset Control and Status Register */
> +#define OA_TC6_STS0 0x0008 /* Status Register #0 */
> +
> +/* RESET register field */
> +#define SW_RESET BIT(0) /* Software Reset */
> +
> +/* STATUS0 register field */
> +#define RESETC BIT(6) /* Reset Complete */
> +
> #define TC6_HDR_SIZE 4 /* Ctrl command header size as per OA */
> #define TC6_FTR_SIZE 4 /* Ctrl command footer size ss per OA */
>
> struct oa_tc6 {
> struct spi_device *spi;
> bool ctrl_prot;
> + struct task_struct *tc6_task;
> + wait_queue_head_t tc6_wq;
> + bool int_flag;
> + struct completion rst_complete;
> };
>
> struct oa_tc6 *oa_tc6_init(struct spi_device *spi);
> -void oa_tc6_deinit(struct oa_tc6 *tc6);
> +int oa_tc6_deinit(struct oa_tc6 *tc6);
> int oa_tc6_write_register(struct oa_tc6 *tc6, u32 addr, u32 value[], u8 len);
> int oa_tc6_read_register(struct oa_tc6 *tc6, u32 addr, u32 value[], u8 len);
>