Re: [PATCH net-next v2 06/11] net: dsa: microchip: ksz9477: basic interrupt support

From: Vladimir Oltean
Date: Thu Nov 12 2020 - 18:26:41 EST


On Thu, Nov 12, 2020 at 04:35:32PM +0100, Christian Eggers wrote:
> Interrupts are required for TX time stamping. Probably they could also
> be used for PHY connection status.

Do the KSZ switches have an internal PHY? And there's a single interrupt
line, shared between the PTP timestamping engine, and the internal PHY
that is driver by phylib?

> This patch only adds the basic infrastructure for interrupts, no
> interrupts are actually enabled nor handled.
>
> ksz9477_reset_switch() must be called before requesting the IRQ (in
> ksz9477_init() instead of ksz9477_setup()).

A patch can never be "too simple". Maybe you could factor out that code
movement into a separate patch.

> Signed-off-by: Christian Eggers <ceggers@xxxxxxx>
> ---
> drivers/net/dsa/microchip/ksz9477_i2c.c | 2 +
> drivers/net/dsa/microchip/ksz9477_main.c | 103 +++++++++++++++++++++--
> drivers/net/dsa/microchip/ksz9477_spi.c | 2 +
> include/linux/dsa/ksz_common.h | 1 +
> 4 files changed, 100 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/net/dsa/microchip/ksz9477_i2c.c b/drivers/net/dsa/microchip/ksz9477_i2c.c
> index 4e053a25d077..4ed1f503044a 100644
> --- a/drivers/net/dsa/microchip/ksz9477_i2c.c
> +++ b/drivers/net/dsa/microchip/ksz9477_i2c.c
> @@ -41,6 +41,8 @@ static int ksz9477_i2c_probe(struct i2c_client *i2c,
> if (i2c->dev.platform_data)
> dev->pdata = i2c->dev.platform_data;
>
> + dev->irq = i2c->irq;
> +
> ret = ksz9477_switch_register(dev);
>
> /* Main DSA driver may not be started yet. */
> diff --git a/drivers/net/dsa/microchip/ksz9477_main.c b/drivers/net/dsa/microchip/ksz9477_main.c
> index abfd3802bb51..6b5a981fb21f 100644
> --- a/drivers/net/dsa/microchip/ksz9477_main.c
> +++ b/drivers/net/dsa/microchip/ksz9477_main.c
> @@ -7,7 +7,9 @@
>
> #include <linux/kernel.h>
> #include <linux/module.h>
> +#include <linux/interrupt.h>
> #include <linux/iopoll.h>
> +#include <linux/irq.h>
> #include <linux/platform_data/microchip-ksz.h>
> #include <linux/phy.h>
> #include <linux/if_bridge.h>
> @@ -1345,19 +1347,12 @@ static void ksz9477_config_cpu_port(struct dsa_switch *ds)
> static int ksz9477_setup(struct dsa_switch *ds)
> {
> struct ksz_device *dev = ds->priv;
> - int ret = 0;
>
> dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
> dev->num_vlans, GFP_KERNEL);
> if (!dev->vlan_cache)
> return -ENOMEM;
>
> - ret = ksz9477_reset_switch(dev);
> - if (ret) {
> - dev_err(ds->dev, "failed to reset switch\n");
> - return ret;
> - }
> -
> /* Required for port partitioning. */
> ksz9477_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY,
> true);
> @@ -1535,12 +1530,84 @@ static const struct ksz_chip_data ksz9477_switch_chips[] = {
> },
> };
>
> +static irqreturn_t ksz9477_switch_irq_thread(int irq, void *dev_id)
> +{
> + struct ksz_device *dev = dev_id;
> + u32 data;
> + int port;
> + int ret;
> + irqreturn_t result = IRQ_NONE;

Please keep local variable declaration sorted in the reverse order of
line length. But....

> +
> + /* Read global port interrupt status register */
> + ret = ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data);
> + if (ret)
> + return result;

...Is there any point at all in keeping the "result" variable?

> +
> + for (port = 0; port < dev->port_cnt; port++) {
> + if (data & BIT(port)) {

You can reduce the indentation level by 1 here using:

if (!(data & BIT(port)))
continue;

> + u8 data8;
> +
> + /* Read port interrupt status register */
> + ret = ksz_read8(dev, PORT_CTRL_ADDR(port, REG_PORT_INT_STATUS),
> + &data8);
> + if (ret)
> + return result;
> +
> + /* ToDo: Add specific handling of port interrupts */

Buggy? Please return IRQ_HANDLED, otherwise the system, when bisected to
this commit exactly, will emit interrupts and complain that nobody cared.

> + }
> + }
> +
> + return result;
> +}
> +
> +static int ksz9477_enable_port_interrupts(struct ksz_device *dev)
> +{
> + u32 data;
> + int ret;
> +
> + ret = ksz_read32(dev, REG_SW_PORT_INT_MASK__4, &data);
> + if (ret)
> + return ret;
> +
> + /* Enable port interrupts (0 means enabled) */
> + data &= ~((1 << dev->port_cnt) - 1);

And what's the " - 1" for?

> + ret = ksz_write32(dev, REG_SW_PORT_INT_MASK__4, data);
> + if (ret)
> + return ret;
> +
> + return 0;

return ksz_write32(dev, REG_SW_PORT_INT_MASK__4, data);

> +}
> +
> +static int ksz9477_disable_port_interrupts(struct ksz_device *dev)
> +{
> + u32 data;
> + int ret;
> +
> + ret = ksz_read32(dev, REG_SW_PORT_INT_MASK__4, &data);
> + if (ret)
> + return ret;
> +
> + /* Disable port interrupts (1 means disabled) */
> + data |= ((1 << dev->port_cnt) - 1);
> + ret = ksz_write32(dev, REG_SW_PORT_INT_MASK__4, data);
> + if (ret)
> + return ret;
> +
> + return 0;

same comments as above.

Also, it's almost as if you want to implement these in the same
function, with a "bool enable"?

> +}
> +
> static int ksz9477_switch_init(struct ksz_device *dev)
> {
> - int i;
> + int i, ret;
>
> dev->ds->ops = &ksz9477_switch_ops;
>
> + ret = ksz9477_reset_switch(dev);
> + if (ret) {
> + dev_err(dev->dev, "failed to reset switch\n");
> + return ret;
> + }
> +
> for (i = 0; i < ARRAY_SIZE(ksz9477_switch_chips); i++) {
> const struct ksz_chip_data *chip = &ksz9477_switch_chips[i];
>
> @@ -1584,12 +1651,32 @@ static int ksz9477_switch_init(struct ksz_device *dev)
>
> /* set the real number of ports */
> dev->ds->num_ports = dev->port_cnt;
> + if (dev->irq > 0) {
> + unsigned long irqflags = irqd_get_trigger_type(irq_get_irq_data(dev->irq));

What is irqd_get_trigger_type and what does it have to do with the
"irqflags" argument of request_threaded_irq? Where else have you even
seen this?

> +
> + irqflags |= IRQF_ONESHOT;

And shared maybe?

> + ret = devm_request_threaded_irq(dev->dev, dev->irq, NULL,
> + ksz9477_switch_irq_thread,
> + irqflags,
> + dev_name(dev->dev),
> + dev);
> + if (ret) {
> + dev_err(dev->dev, "failed to request IRQ.\n");
> + return ret;
> + }
> +
> + ret = ksz9477_enable_port_interrupts(dev);
> + if (ret)
> + return ret;

Could you also clear pending interrupts before enabling the line?

> + }
>
> return 0;
> }
>
> static void ksz9477_switch_exit(struct ksz_device *dev)
> {
> + if (dev->irq > 0)
> + ksz9477_disable_port_interrupts(dev);

I think it'd look a bit nicer if you moved this condition into
ksz9477_disable_port_interrupts:

if (!dev->irq)
return;

> ksz9477_reset_switch(dev);
> }
>