@@ -2014,13 +2014,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);
@@ -2037,6 +2041,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)
@@ -2056,8 +2064,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);
}
@@ -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;
@@ -105,6 +113,8 @@ struct ksz_port {
u8 num;
#if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ_PTP)
u8 hwts_tx_en;
+ struct ksz_irq ptpirq;
+ struct ksz_ptp_irq ptpmsg_irq[3];
#endif
};
@@ -606,6 +616,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
/* Regmap tables generation */
#define KSZ_SPI_OP_RD 3
@@ -4,6 +4,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>
@@ -20,6 +22,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, bool enable)
{
struct ksz_ptp_data *ptp_data = &dev->ptp_data;
@@ -402,6 +406,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");
@@ -28,6 +28,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
@@ -57,6 +59,13 @@ static inline int ksz_hwtstamp_set(struct dsa_switch *ds, int port,
return -EOPNOTSUPP;
}
+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) {}
+
#endif /* End of CONFIG_NET_DSA_MICROCHIP_KSZ_PTP */
#endif
@@ -54,4 +54,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 reserver 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(-)