Message ID | 20221209072437.18373-6-arun.ramadoss@microchip.com (mailing list archive) |
---|---|
State | Superseded |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | net: dsa: microchip: add PTP support for KSZ9563/KSZ8563 and LAN937x | expand |
Context | Check | Description |
---|---|---|
netdev/tree_selection | success | Clearly marked for net-next |
netdev/fixes_present | success | Fixes tag not required for -next series |
netdev/subject_prefix | success | Link |
netdev/cover_letter | success | Series has a cover letter |
netdev/patch_count | success | Link |
netdev/header_inline | success | No static functions without inline keyword in header files |
netdev/build_32bit | success | Errors and warnings before: 0 this patch: 0 |
netdev/cc_maintainers | success | CCed 11 of 11 maintainers |
netdev/build_clang | success | Errors and warnings before: 0 this patch: 0 |
netdev/module_param | success | Was 0 now: 0 |
netdev/verify_signedoff | success | Signed-off-by tag matches author and committer |
netdev/check_selftest | success | No net selftest shell script |
netdev/verify_fixes | success | No Fixes tag |
netdev/build_allmodconfig_warn | success | Errors and warnings before: 0 this patch: 0 |
netdev/checkpatch | success | total: 0 errors, 0 warnings, 0 checks, 337 lines checked |
netdev/kdoc | success | Errors and warnings before: 0 this patch: 0 |
netdev/source_inline | success | Was 0 now: 0 |
Hi Arun, On Friday, 9 December 2022, 08:24:29 CET, Arun Ramadoss wrote: > PTP Interrupt mask and status register differ from the global and port > interrupt mechanism by two methods. One is that for global/port > interrupt enabling we have to clear the bit but for ptp interrupt we > have to set the bit. And other is bit12:0 is reserved in ptp interrupt > registers. This forced to not use the generic implementation of > global/port interrupt method routine. This patch implement the ptp > interrupt mechanism to read the timestamp register for sync, pdelay_req > and pdelay_resp. > > Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> > > --- > RFC v2 -> Patch v1 > - Moved the acking of interrupts before calling the handle_nested_irq to > avoid race condition between deferred xmit and Irq threads > --- > drivers/net/dsa/microchip/ksz_common.c | 15 +- > drivers/net/dsa/microchip/ksz_common.h | 11 ++ > drivers/net/dsa/microchip/ksz_ptp.c | 200 ++++++++++++++++++++++++ > drivers/net/dsa/microchip/ksz_ptp.h | 9 ++ > drivers/net/dsa/microchip/ksz_ptp_reg.h | 19 +++ > 5 files changed, 252 insertions(+), 2 deletions(-) > > diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c > index 81da650b70fb..1611f8f5cd6c 100644 > --- a/drivers/net/dsa/microchip/ksz_common.c > +++ b/drivers/net/dsa/microchip/ksz_common.c > @@ -2101,13 +2101,17 @@ static int ksz_setup(struct dsa_switch *ds) > ret = ksz_pirq_setup(dev, dp->index); > if (ret) > goto out_girq; > + > + ret = ksz_ptp_irq_setup(ds, dp->index); > + if (ret) > + goto out_pirq; > } > } > > ret = ksz_ptp_clock_register(ds); > if (ret) { > dev_err(dev->dev, "Failed to register PTP clock: %d\n", ret); > - goto out_pirq; > + goto out_ptpirq; > } > > ret = ksz_mdio_register(dev); > @@ -2124,6 +2128,10 @@ static int ksz_setup(struct dsa_switch *ds) > > out_ptp_clock_unregister: > ksz_ptp_clock_unregister(ds); > +out_ptpirq: > + if (dev->irq > 0) > + dsa_switch_for_each_user_port(dp, dev->ds) > + ksz_ptp_irq_free(ds, dp->index); > out_pirq: > if (dev->irq > 0) > dsa_switch_for_each_user_port(dp, dev->ds) > @@ -2143,8 +2151,11 @@ static void ksz_teardown(struct dsa_switch *ds) > ksz_ptp_clock_unregister(ds); > > if (dev->irq > 0) { > - dsa_switch_for_each_user_port(dp, dev->ds) > + dsa_switch_for_each_user_port(dp, dev->ds) { > + ksz_ptp_irq_free(ds, dp->index); > + > ksz_irq_free(&dev->ports[dp->index].pirq); > + } > > ksz_irq_free(&dev->girq); > } > diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h > index 641aca78ef05..918028db2e6f 100644 > --- a/drivers/net/dsa/microchip/ksz_common.h > +++ b/drivers/net/dsa/microchip/ksz_common.h > @@ -20,6 +20,7 @@ > #define KSZ_MAX_NUM_PORTS 8 > > struct ksz_device; > +struct ksz_port; > > struct vlan_table { > u32 table[3]; > @@ -83,6 +84,13 @@ struct ksz_irq { > struct ksz_device *dev; > }; > > +struct ksz_ptp_irq { > + struct ksz_port *port; > + u16 ts_reg; > + char name[16]; > + int irq_num; > +}; > + > struct ksz_port { > bool remove_tag; /* Remove Tag flag set, for ksz8795 only */ > bool learning; > @@ -106,6 +114,8 @@ struct ksz_port { > struct hwtstamp_config tstamp_config; > bool hwts_tx_en; > bool hwts_rx_en; > + struct ksz_irq ptpirq; > + struct ksz_ptp_irq ptpmsg_irq[3]; > #endif > }; > > @@ -612,6 +622,7 @@ static inline int is_lan937x(struct ksz_device *dev) > #define REG_PORT_INT_MASK 0x001F > > #define PORT_SRC_PHY_INT 1 > +#define PORT_SRC_PTP_INT 2 > > #define KSZ8795_HUGE_PACKET_SIZE 2000 > #define KSZ8863_HUGE_PACKET_SIZE 1916 > diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c > index b864b88dc6f9..a7b015ac2734 100644 > --- a/drivers/net/dsa/microchip/ksz_ptp.c > +++ b/drivers/net/dsa/microchip/ksz_ptp.c > @@ -6,6 +6,8 @@ > */ > > #include <linux/dsa/ksz_common.h> > +#include <linux/irq.h> > +#include <linux/irqdomain.h> > #include <linux/kernel.h> > #include <linux/ptp_classify.h> > #include <linux/ptp_clock_kernel.h> > @@ -25,6 +27,8 @@ > #define KSZ_PTP_INC_NS 40 /* HW clock is incremented every 40 ns (by 40) */ > #define KSZ_PTP_SUBNS_BITS 32 > > +#define KSZ_PTP_INT_START 13 > + > static int ksz_ptp_enable_mode(struct ksz_device *dev) > { > struct ksz_tagger_data *tagger_data = ksz_tagger_data(dev->ds); > @@ -415,6 +419,202 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds) > ptp_clock_unregister(ptp_data->clock); > } > > +static irqreturn_t ksz_ptp_msg_thread_fn(int irq, void *dev_id) > +{ > + return IRQ_NONE; > +} > + > +static irqreturn_t ksz_ptp_irq_thread_fn(int irq, void *dev_id) > +{ > + struct ksz_irq *ptpirq = dev_id; > + unsigned int nhandled = 0; > + struct ksz_device *dev; > + unsigned int sub_irq; > + u16 data; > + int ret; > + u8 n; > + > + dev = ptpirq->dev; > + > + ret = ksz_read16(dev, ptpirq->reg_status, &data); > + if (ret) > + goto out; > + > + /* Clear the interrupts W1C */ > + ret = ksz_write16(dev, ptpirq->reg_status, data); > + if (ret) > + return IRQ_NONE; > + > + for (n = 0; n < ptpirq->nirqs; ++n) { > + if (data & BIT(n + KSZ_PTP_INT_START)) { > + sub_irq = irq_find_mapping(ptpirq->domain, n); > + handle_nested_irq(sub_irq); > + ++nhandled; > + } > + } > + > +out: > + return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); > +} > + > +static void ksz_ptp_irq_mask(struct irq_data *d) > +{ > + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); > + > + kirq->masked &= ~BIT(d->hwirq + KSZ_PTP_INT_START); > +} > + > +static void ksz_ptp_irq_unmask(struct irq_data *d) > +{ > + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); > + > + kirq->masked |= BIT(d->hwirq + KSZ_PTP_INT_START); > +} > + > +static void ksz_ptp_irq_bus_lock(struct irq_data *d) > +{ > + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); > + > + mutex_lock(&kirq->dev->lock_irq); > +} > + > +static void ksz_ptp_irq_bus_sync_unlock(struct irq_data *d) > +{ > + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); > + struct ksz_device *dev = kirq->dev; > + int ret; > + > + ret = ksz_write16(dev, kirq->reg_mask, kirq->masked); > + if (ret) > + dev_err(dev->dev, "failed to change IRQ mask\n"); > + > + mutex_unlock(&dev->lock_irq); > +} > + > +static const struct irq_chip ksz_ptp_irq_chip = { > + .name = "ksz-irq", > + .irq_mask = ksz_ptp_irq_mask, > + .irq_unmask = ksz_ptp_irq_unmask, > + .irq_bus_lock = ksz_ptp_irq_bus_lock, > + .irq_bus_sync_unlock = ksz_ptp_irq_bus_sync_unlock, > +}; > + > +static int ksz_ptp_irq_domain_map(struct irq_domain *d, > + unsigned int irq, irq_hw_number_t hwirq) > +{ > + irq_set_chip_data(irq, d->host_data); > + irq_set_chip_and_handler(irq, &ksz_ptp_irq_chip, handle_level_irq); > + irq_set_noprobe(irq); > + > + return 0; > +} > + > +static const struct irq_domain_ops ksz_ptp_irq_domain_ops = { > + .map = ksz_ptp_irq_domain_map, > + .xlate = irq_domain_xlate_twocell, > +}; > + > +static int ksz_ptp_msg_irq_setup(struct ksz_port *port) > +{ > + u16 ts_reg[] = {REG_PTP_PORT_PDRESP_TS, REG_PTP_PORT_XDELAY_TS, > + REG_PTP_PORT_SYNC_TS}; > + struct ksz_device *dev = port->ksz_dev; > + struct ksz_irq *ptpirq = &port->ptpirq; > + struct ksz_ptp_irq *ptpmsg_irq; > + int ret; > + u8 n; > + > + for (n = 0; n < ptpirq->nirqs; n++) { > + ptpmsg_irq = &port->ptpmsg_irq[n]; > + > + ptpmsg_irq->port = port; > + ptpmsg_irq->ts_reg = dev->dev_ops->get_port_addr(port->num, > + ts_reg[n]); > + ptpmsg_irq->irq_num = irq_create_mapping(ptpirq->domain, n); > + if (ptpmsg_irq->irq_num < 0) { > + ret = ptpmsg_irq->irq_num; > + goto out; > + } > + > + snprintf(ptpmsg_irq->name, sizeof(ptpmsg_irq->name), > + "PTP-MSG-%d", n); > + > + ret = request_threaded_irq(ptpmsg_irq->irq_num, NULL, > + ksz_ptp_msg_thread_fn, > + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, I assume that IRQF_TRIGGER_FALLING is not required here as nested interrupts are fired by software (withing having an edge / a level). Additionally I had to remove all existing occurrences of this flag in ksz_common.c in order to rid of persistent "timed out while polling for tx timestamp" messages which appeared randomly after some time of operation. I think that on i.MX6 I need to use level triggered interrupts instead of edge triggered ones. Additionally I think that such flags should be set in the device tree instead of the driver: https://stackoverflow.com/a/40051191 > + ptpmsg_irq->name, ptpmsg_irq); > + if (ret) > + goto out; > + } > + > + return 0; > + > +out: > + while (n--) > + irq_dispose_mapping(port->ptpmsg_irq[n].irq_num); > + > + return ret; > +} > + > +int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) > +{ > + struct ksz_device *dev = ds->priv; > + const struct ksz_dev_ops *ops = dev->dev_ops; > + struct ksz_port *port = &dev->ports[p]; > + struct ksz_irq *ptpirq = &port->ptpirq; > + int ret; > + > + ptpirq->dev = dev; > + ptpirq->masked = 0; > + ptpirq->nirqs = 3; > + ptpirq->reg_mask = ops->get_port_addr(p, REG_PTP_PORT_TX_INT_ENABLE__2); > + ptpirq->reg_status = ops->get_port_addr(p, > + REG_PTP_PORT_TX_INT_STATUS__2); > + snprintf(ptpirq->name, sizeof(ptpirq->name), "ptp_irq-%d", p); > + > + ptpirq->irq_num = irq_find_mapping(port->pirq.domain, PORT_SRC_PTP_INT); > + if (ptpirq->irq_num < 0) > + return ptpirq->irq_num; > + > + ptpirq->domain = irq_domain_add_simple(dev->dev->of_node, ptpirq->nirqs, > + 0, &ksz_ptp_irq_domain_ops, > + ptpirq); > + if (!ptpirq->domain) > + return -ENOMEM; > + > + ret = request_threaded_irq(ptpirq->irq_num, NULL, ksz_ptp_irq_thread_fn, > + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, > + ptpirq->name, ptpirq); > + if (ret) > + goto out; > + > + ret = ksz_ptp_msg_irq_setup(port); > + if (ret) > + goto out; > + > + return 0; > + > +out: > + irq_dispose_mapping(ptpirq->irq_num); > + > + return ret; > +} > + > +void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p) > +{ > + struct ksz_device *dev = ds->priv; > + struct ksz_port *port = &dev->ports[p]; > + struct ksz_irq *ptpirq = &port->ptpirq; > + u8 n; > + > + free_irq(ptpirq->irq_num, ptpirq); > + > + for (n = 0; n < ptpirq->nirqs; n++) > + irq_dispose_mapping(port->ptpmsg_irq[n].irq_num); > + > + irq_domain_remove(ptpirq->domain); > +} > + > MODULE_AUTHOR("Christian Eggers <ceggers@arri.de>"); > MODULE_AUTHOR("Arun Ramadoss <arun.ramadoss@microchip.com>"); > MODULE_DESCRIPTION("PTP support for KSZ switch"); > diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h > index 2c29a0b604bb..7c5679372705 100644 > --- a/drivers/net/dsa/microchip/ksz_ptp.h > +++ b/drivers/net/dsa/microchip/ksz_ptp.h > @@ -30,6 +30,8 @@ int ksz_get_ts_info(struct dsa_switch *ds, int port, > struct ethtool_ts_info *ts); > int ksz_hwtstamp_get(struct dsa_switch *ds, int port, struct ifreq *ifr); > int ksz_hwtstamp_set(struct dsa_switch *ds, int port, struct ifreq *ifr); > +int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p); > +void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p); > > #else > > @@ -45,6 +47,13 @@ static inline int ksz_ptp_clock_register(struct dsa_switch *ds) > > static inline void ksz_ptp_clock_unregister(struct dsa_switch *ds) { } > > +static inline int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) > +{ > + return 0; > +} > + > +static inline void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p) {} > + > #define ksz_get_ts_info NULL > > #define ksz_hwtstamp_get NULL > diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h > index 4ca4ad4bba7e..abe95bbefc12 100644 > --- a/drivers/net/dsa/microchip/ksz_ptp_reg.h > +++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h > @@ -49,4 +49,23 @@ > #define PTP_MASTER BIT(1) > #define PTP_1STEP BIT(0) > > +/* Port PTP Register */ > +#define REG_PTP_PORT_RX_DELAY__2 0x0C00 > +#define REG_PTP_PORT_TX_DELAY__2 0x0C02 > +#define REG_PTP_PORT_ASYM_DELAY__2 0x0C04 > + > +#define REG_PTP_PORT_XDELAY_TS 0x0C08 > +#define REG_PTP_PORT_SYNC_TS 0x0C0C > +#define REG_PTP_PORT_PDRESP_TS 0x0C10 > + > +#define REG_PTP_PORT_TX_INT_STATUS__2 0x0C14 > +#define REG_PTP_PORT_TX_INT_ENABLE__2 0x0C16 > + > +#define PTP_PORT_SYNC_INT BIT(15) > +#define PTP_PORT_XDELAY_REQ_INT BIT(14) > +#define PTP_PORT_PDELAY_RESP_INT BIT(13) > +#define KSZ_SYNC_MSG 2 > +#define KSZ_XDREQ_MSG 1 > +#define KSZ_PDRES_MSG 0 > + > #endif >
Hi Christian, Thanks for the comment. On Fri, 2022-12-09 at 20:40 +0100, Christian Eggers wrote: > EXTERNAL EMAIL: Do not click links or open attachments unless you > know the content is safe > > Hi Arun, > > On Friday, 9 December 2022, 08:24:29 CET, Arun Ramadoss wrote: > > PTP Interrupt mask and status register differ from the global and > > port > > interrupt mechanism by two methods. One is that for global/port > > interrupt enabling we have to clear the bit but for ptp interrupt > > we > > have to set the bit. And other is bit12:0 is reserved in ptp > > interrupt > > registers. This forced to not use the generic implementation of > > global/port interrupt method routine. This patch implement the ptp > > interrupt mechanism to read the timestamp register for sync, > > pdelay_req > > and pdelay_resp. > > > > Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> > > > > + > > + ret = request_threaded_irq(ptpmsg_irq->irq_num, NULL, > > + ksz_ptp_msg_thread_fn, > > + IRQF_ONESHOT | > > IRQF_TRIGGER_FALLING, > > I assume that IRQF_TRIGGER_FALLING is not required here as nested > interrupts are fired by > software (withing having an edge / a level). Additionally I had to > remove all existing > occurrences of this flag in ksz_common.c in order to rid of > persistent "timed out while > polling for tx timestamp" messages which appeared randomly after some > time of operation. > I think that on i.MX6 I need to use level triggered interrupts > instead of edge triggered > ones. Additionally I think that such flags should be set in the > device tree instead of > the driver: > > https://stackoverflow.com/a/40051191 I will remove IRQF_TRIGGER_FALLING from the ksz_ptp.c in next patch version. To remove the flag in ksz_common.c, I will send bug fix patch separately. > > > > + if (ret) > > + goto out; > > + } > > + > > + return 0; > > + > > +out: > > + while (n--) > > + irq_dispose_mapping(port->ptpmsg_irq[n].irq_num); > > + > > + return ret; > > +} > > > > > > > >
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 81da650b70fb..1611f8f5cd6c 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -2101,13 +2101,17 @@ static int ksz_setup(struct dsa_switch *ds) ret = ksz_pirq_setup(dev, dp->index); if (ret) goto out_girq; + + ret = ksz_ptp_irq_setup(ds, dp->index); + if (ret) + goto out_pirq; } } ret = ksz_ptp_clock_register(ds); if (ret) { dev_err(dev->dev, "Failed to register PTP clock: %d\n", ret); - goto out_pirq; + goto out_ptpirq; } ret = ksz_mdio_register(dev); @@ -2124,6 +2128,10 @@ static int ksz_setup(struct dsa_switch *ds) out_ptp_clock_unregister: ksz_ptp_clock_unregister(ds); +out_ptpirq: + if (dev->irq > 0) + dsa_switch_for_each_user_port(dp, dev->ds) + ksz_ptp_irq_free(ds, dp->index); out_pirq: if (dev->irq > 0) dsa_switch_for_each_user_port(dp, dev->ds) @@ -2143,8 +2151,11 @@ static void ksz_teardown(struct dsa_switch *ds) ksz_ptp_clock_unregister(ds); if (dev->irq > 0) { - dsa_switch_for_each_user_port(dp, dev->ds) + dsa_switch_for_each_user_port(dp, dev->ds) { + ksz_ptp_irq_free(ds, dp->index); + ksz_irq_free(&dev->ports[dp->index].pirq); + } ksz_irq_free(&dev->girq); } diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 641aca78ef05..918028db2e6f 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -20,6 +20,7 @@ #define KSZ_MAX_NUM_PORTS 8 struct ksz_device; +struct ksz_port; struct vlan_table { u32 table[3]; @@ -83,6 +84,13 @@ struct ksz_irq { struct ksz_device *dev; }; +struct ksz_ptp_irq { + struct ksz_port *port; + u16 ts_reg; + char name[16]; + int irq_num; +}; + struct ksz_port { bool remove_tag; /* Remove Tag flag set, for ksz8795 only */ bool learning; @@ -106,6 +114,8 @@ struct ksz_port { struct hwtstamp_config tstamp_config; bool hwts_tx_en; bool hwts_rx_en; + struct ksz_irq ptpirq; + struct ksz_ptp_irq ptpmsg_irq[3]; #endif }; @@ -612,6 +622,7 @@ static inline int is_lan937x(struct ksz_device *dev) #define REG_PORT_INT_MASK 0x001F #define PORT_SRC_PHY_INT 1 +#define PORT_SRC_PTP_INT 2 #define KSZ8795_HUGE_PACKET_SIZE 2000 #define KSZ8863_HUGE_PACKET_SIZE 1916 diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index b864b88dc6f9..a7b015ac2734 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -6,6 +6,8 @@ */ #include <linux/dsa/ksz_common.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> #include <linux/kernel.h> #include <linux/ptp_classify.h> #include <linux/ptp_clock_kernel.h> @@ -25,6 +27,8 @@ #define KSZ_PTP_INC_NS 40 /* HW clock is incremented every 40 ns (by 40) */ #define KSZ_PTP_SUBNS_BITS 32 +#define KSZ_PTP_INT_START 13 + static int ksz_ptp_enable_mode(struct ksz_device *dev) { struct ksz_tagger_data *tagger_data = ksz_tagger_data(dev->ds); @@ -415,6 +419,202 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds) ptp_clock_unregister(ptp_data->clock); } +static irqreturn_t ksz_ptp_msg_thread_fn(int irq, void *dev_id) +{ + return IRQ_NONE; +} + +static irqreturn_t ksz_ptp_irq_thread_fn(int irq, void *dev_id) +{ + struct ksz_irq *ptpirq = dev_id; + unsigned int nhandled = 0; + struct ksz_device *dev; + unsigned int sub_irq; + u16 data; + int ret; + u8 n; + + dev = ptpirq->dev; + + ret = ksz_read16(dev, ptpirq->reg_status, &data); + if (ret) + goto out; + + /* Clear the interrupts W1C */ + ret = ksz_write16(dev, ptpirq->reg_status, data); + if (ret) + return IRQ_NONE; + + for (n = 0; n < ptpirq->nirqs; ++n) { + if (data & BIT(n + KSZ_PTP_INT_START)) { + sub_irq = irq_find_mapping(ptpirq->domain, n); + handle_nested_irq(sub_irq); + ++nhandled; + } + } + +out: + return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE); +} + +static void ksz_ptp_irq_mask(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + kirq->masked &= ~BIT(d->hwirq + KSZ_PTP_INT_START); +} + +static void ksz_ptp_irq_unmask(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + kirq->masked |= BIT(d->hwirq + KSZ_PTP_INT_START); +} + +static void ksz_ptp_irq_bus_lock(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + + mutex_lock(&kirq->dev->lock_irq); +} + +static void ksz_ptp_irq_bus_sync_unlock(struct irq_data *d) +{ + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); + struct ksz_device *dev = kirq->dev; + int ret; + + ret = ksz_write16(dev, kirq->reg_mask, kirq->masked); + if (ret) + dev_err(dev->dev, "failed to change IRQ mask\n"); + + mutex_unlock(&dev->lock_irq); +} + +static const struct irq_chip ksz_ptp_irq_chip = { + .name = "ksz-irq", + .irq_mask = ksz_ptp_irq_mask, + .irq_unmask = ksz_ptp_irq_unmask, + .irq_bus_lock = ksz_ptp_irq_bus_lock, + .irq_bus_sync_unlock = ksz_ptp_irq_bus_sync_unlock, +}; + +static int ksz_ptp_irq_domain_map(struct irq_domain *d, + unsigned int irq, irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &ksz_ptp_irq_chip, handle_level_irq); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops ksz_ptp_irq_domain_ops = { + .map = ksz_ptp_irq_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int ksz_ptp_msg_irq_setup(struct ksz_port *port) +{ + u16 ts_reg[] = {REG_PTP_PORT_PDRESP_TS, REG_PTP_PORT_XDELAY_TS, + REG_PTP_PORT_SYNC_TS}; + struct ksz_device *dev = port->ksz_dev; + struct ksz_irq *ptpirq = &port->ptpirq; + struct ksz_ptp_irq *ptpmsg_irq; + int ret; + u8 n; + + for (n = 0; n < ptpirq->nirqs; n++) { + ptpmsg_irq = &port->ptpmsg_irq[n]; + + ptpmsg_irq->port = port; + ptpmsg_irq->ts_reg = dev->dev_ops->get_port_addr(port->num, + ts_reg[n]); + ptpmsg_irq->irq_num = irq_create_mapping(ptpirq->domain, n); + if (ptpmsg_irq->irq_num < 0) { + ret = ptpmsg_irq->irq_num; + goto out; + } + + snprintf(ptpmsg_irq->name, sizeof(ptpmsg_irq->name), + "PTP-MSG-%d", n); + + ret = request_threaded_irq(ptpmsg_irq->irq_num, NULL, + ksz_ptp_msg_thread_fn, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, + ptpmsg_irq->name, ptpmsg_irq); + if (ret) + goto out; + } + + return 0; + +out: + while (n--) + irq_dispose_mapping(port->ptpmsg_irq[n].irq_num); + + return ret; +} + +int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) +{ + struct ksz_device *dev = ds->priv; + const struct ksz_dev_ops *ops = dev->dev_ops; + struct ksz_port *port = &dev->ports[p]; + struct ksz_irq *ptpirq = &port->ptpirq; + int ret; + + ptpirq->dev = dev; + ptpirq->masked = 0; + ptpirq->nirqs = 3; + ptpirq->reg_mask = ops->get_port_addr(p, REG_PTP_PORT_TX_INT_ENABLE__2); + ptpirq->reg_status = ops->get_port_addr(p, + REG_PTP_PORT_TX_INT_STATUS__2); + snprintf(ptpirq->name, sizeof(ptpirq->name), "ptp_irq-%d", p); + + ptpirq->irq_num = irq_find_mapping(port->pirq.domain, PORT_SRC_PTP_INT); + if (ptpirq->irq_num < 0) + return ptpirq->irq_num; + + ptpirq->domain = irq_domain_add_simple(dev->dev->of_node, ptpirq->nirqs, + 0, &ksz_ptp_irq_domain_ops, + ptpirq); + if (!ptpirq->domain) + return -ENOMEM; + + ret = request_threaded_irq(ptpirq->irq_num, NULL, ksz_ptp_irq_thread_fn, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, + ptpirq->name, ptpirq); + if (ret) + goto out; + + ret = ksz_ptp_msg_irq_setup(port); + if (ret) + goto out; + + return 0; + +out: + irq_dispose_mapping(ptpirq->irq_num); + + return ret; +} + +void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p) +{ + struct ksz_device *dev = ds->priv; + struct ksz_port *port = &dev->ports[p]; + struct ksz_irq *ptpirq = &port->ptpirq; + u8 n; + + free_irq(ptpirq->irq_num, ptpirq); + + for (n = 0; n < ptpirq->nirqs; n++) + irq_dispose_mapping(port->ptpmsg_irq[n].irq_num); + + irq_domain_remove(ptpirq->domain); +} + MODULE_AUTHOR("Christian Eggers <ceggers@arri.de>"); MODULE_AUTHOR("Arun Ramadoss <arun.ramadoss@microchip.com>"); MODULE_DESCRIPTION("PTP support for KSZ switch"); diff --git a/drivers/net/dsa/microchip/ksz_ptp.h b/drivers/net/dsa/microchip/ksz_ptp.h index 2c29a0b604bb..7c5679372705 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.h +++ b/drivers/net/dsa/microchip/ksz_ptp.h @@ -30,6 +30,8 @@ int ksz_get_ts_info(struct dsa_switch *ds, int port, struct ethtool_ts_info *ts); int ksz_hwtstamp_get(struct dsa_switch *ds, int port, struct ifreq *ifr); int ksz_hwtstamp_set(struct dsa_switch *ds, int port, struct ifreq *ifr); +int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p); +void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p); #else @@ -45,6 +47,13 @@ static inline int ksz_ptp_clock_register(struct dsa_switch *ds) static inline void ksz_ptp_clock_unregister(struct dsa_switch *ds) { } +static inline int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) +{ + return 0; +} + +static inline void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p) {} + #define ksz_get_ts_info NULL #define ksz_hwtstamp_get NULL diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h index 4ca4ad4bba7e..abe95bbefc12 100644 --- a/drivers/net/dsa/microchip/ksz_ptp_reg.h +++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h @@ -49,4 +49,23 @@ #define PTP_MASTER BIT(1) #define PTP_1STEP BIT(0) +/* Port PTP Register */ +#define REG_PTP_PORT_RX_DELAY__2 0x0C00 +#define REG_PTP_PORT_TX_DELAY__2 0x0C02 +#define REG_PTP_PORT_ASYM_DELAY__2 0x0C04 + +#define REG_PTP_PORT_XDELAY_TS 0x0C08 +#define REG_PTP_PORT_SYNC_TS 0x0C0C +#define REG_PTP_PORT_PDRESP_TS 0x0C10 + +#define REG_PTP_PORT_TX_INT_STATUS__2 0x0C14 +#define REG_PTP_PORT_TX_INT_ENABLE__2 0x0C16 + +#define PTP_PORT_SYNC_INT BIT(15) +#define PTP_PORT_XDELAY_REQ_INT BIT(14) +#define PTP_PORT_PDELAY_RESP_INT BIT(13) +#define KSZ_SYNC_MSG 2 +#define KSZ_XDREQ_MSG 1 +#define KSZ_PDRES_MSG 0 + #endif
PTP Interrupt mask and status register differ from the global and port interrupt mechanism by two methods. One is that for global/port interrupt enabling we have to clear the bit but for ptp interrupt we have to set the bit. And other is bit12:0 is reserved in ptp interrupt registers. This forced to not use the generic implementation of global/port interrupt method routine. This patch implement the ptp interrupt mechanism to read the timestamp register for sync, pdelay_req and pdelay_resp. Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> --- RFC v2 -> Patch v1 - Moved the acking of interrupts before calling the handle_nested_irq to avoid race condition between deferred xmit and Irq threads --- drivers/net/dsa/microchip/ksz_common.c | 15 +- drivers/net/dsa/microchip/ksz_common.h | 11 ++ drivers/net/dsa/microchip/ksz_ptp.c | 200 ++++++++++++++++++++++++ drivers/net/dsa/microchip/ksz_ptp.h | 9 ++ drivers/net/dsa/microchip/ksz_ptp_reg.h | 19 +++ 5 files changed, 252 insertions(+), 2 deletions(-)