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, ®val, 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, + ®val, 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, ®val, 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, ®val, 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); + 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); -- 2.34.1