Commit 4e3b0468 authored by Antoine Tenart's avatar Antoine Tenart Committed by David S. Miller

net: mscc: PTP Hardware Clock (PHC) support

This patch adds support for PTP Hardware Clock (PHC) to the Ocelot
switch for both PTP 1-step and 2-step modes.
Signed-off-by: default avatarAntoine Tenart <antoine.tenart@bootlin.com>
Acked-by: default avatarRichard Cochran <richardcochran@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 1f0239de
This diff is collapsed.
......@@ -11,9 +11,11 @@
#include <linux/bitops.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/net_tstamp.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/regmap.h>
#include "ocelot_ana.h"
......@@ -39,6 +41,8 @@
#define OCELOT_STATS_CHECK_DELAY (2 * HZ)
#define OCELOT_PTP_QUEUE_SZ 128
#define IFH_LEN 4
struct frame_info {
......@@ -46,6 +50,8 @@ struct frame_info {
u16 port;
u16 vid;
u8 tag_type;
u16 rew_op;
u32 timestamp; /* rew_val */
};
#define IFH_INJ_BYPASS BIT(31)
......@@ -54,6 +60,12 @@ struct frame_info {
#define IFH_TAG_TYPE_C 0
#define IFH_TAG_TYPE_S 1
#define IFH_REW_OP_NOOP 0x0
#define IFH_REW_OP_DSCP 0x1
#define IFH_REW_OP_ONE_STEP_PTP 0x2
#define IFH_REW_OP_TWO_STEP_PTP 0x3
#define IFH_REW_OP_ORIGIN_PTP 0x5
#define OCELOT_SPEED_2500 0
#define OCELOT_SPEED_1000 1
#define OCELOT_SPEED_100 2
......@@ -401,6 +413,13 @@ enum ocelot_regfield {
REGFIELD_MAX
};
enum ocelot_clk_pins {
ALT_PPS_PIN = 1,
EXT_CLK_PIN,
ALT_LDST_PIN,
TOD_ACC_PIN
};
struct ocelot_multicast {
struct list_head list;
unsigned char addr[ETH_ALEN];
......@@ -450,6 +469,13 @@ struct ocelot {
u64 *stats;
struct delayed_work stats_work;
struct workqueue_struct *stats_queue;
u8 ptp:1;
struct ptp_clock *ptp_clock;
struct ptp_clock_info ptp_info;
struct hwtstamp_config hwtstamp_config;
struct mutex ptp_lock; /* Protects the PTP interface state */
spinlock_t ptp_clock_lock; /* Protects the PTP clock */
};
struct ocelot_port {
......@@ -473,6 +499,16 @@ struct ocelot_port {
struct phy *serdes;
struct ocelot_port_tc tc;
u8 ptp_cmd;
struct list_head skbs;
u8 ts_id;
};
struct ocelot_skb {
struct list_head head;
struct sk_buff *skb;
u8 id;
};
u32 __ocelot_read_ix(struct ocelot *ocelot, u32 reg, u32 offset);
......@@ -517,4 +553,7 @@ extern struct notifier_block ocelot_netdevice_nb;
extern struct notifier_block ocelot_switchdev_nb;
extern struct notifier_block ocelot_switchdev_blocking_nb;
int ocelot_ptp_gettime64(struct ptp_clock_info *ptp, struct timespec64 *ts);
void ocelot_get_hwtimestamp(struct ocelot *ocelot, struct timespec64 *ts);
#endif
......@@ -31,6 +31,8 @@ static int ocelot_parse_ifh(u32 *_ifh, struct frame_info *info)
info->len = OCELOT_BUFFER_CELL_SZ * wlen + llen - 80;
info->timestamp = IFH_EXTRACT_BITFIELD64(ifh[0], 21, 32);
info->port = IFH_EXTRACT_BITFIELD64(ifh[1], 43, 4);
info->tag_type = IFH_EXTRACT_BITFIELD64(ifh[1], 16, 1);
......@@ -92,13 +94,14 @@ static irqreturn_t ocelot_xtr_irq_handler(int irq, void *arg)
return IRQ_NONE;
do {
struct sk_buff *skb;
struct skb_shared_hwtstamps *shhwtstamps;
u64 tod_in_ns, full_ts_in_ns;
struct frame_info info = {};
struct net_device *dev;
u32 *buf;
u32 ifh[4], val, *buf;
struct timespec64 ts;
int sz, len, buf_len;
u32 ifh[4];
u32 val;
struct frame_info info;
struct sk_buff *skb;
for (i = 0; i < IFH_LEN; i++) {
err = ocelot_rx_frame_word(ocelot, grp, true, &ifh[i]);
......@@ -145,6 +148,22 @@ static irqreturn_t ocelot_xtr_irq_handler(int irq, void *arg)
break;
}
if (ocelot->ptp) {
ocelot_ptp_gettime64(&ocelot->ptp_info, &ts);
tod_in_ns = ktime_set(ts.tv_sec, ts.tv_nsec);
if ((tod_in_ns & 0xffffffff) < info.timestamp)
full_ts_in_ns = (((tod_in_ns >> 32) - 1) << 32) |
info.timestamp;
else
full_ts_in_ns = (tod_in_ns & GENMASK_ULL(63, 32)) |
info.timestamp;
shhwtstamps = skb_hwtstamps(skb);
memset(shhwtstamps, 0, sizeof(struct skb_shared_hwtstamps));
shhwtstamps->hwtstamp = full_ts_in_ns;
}
/* Everything we see on an interface that is in the HW bridge
* has already been forwarded.
*/
......@@ -164,6 +183,66 @@ static irqreturn_t ocelot_xtr_irq_handler(int irq, void *arg)
return IRQ_HANDLED;
}
static irqreturn_t ocelot_ptp_rdy_irq_handler(int irq, void *arg)
{
int budget = OCELOT_PTP_QUEUE_SZ;
struct ocelot *ocelot = arg;
while (budget--) {
struct skb_shared_hwtstamps shhwtstamps;
struct list_head *pos, *tmp;
struct sk_buff *skb = NULL;
struct ocelot_skb *entry;
struct ocelot_port *port;
struct timespec64 ts;
u32 val, id, txport;
val = ocelot_read(ocelot, SYS_PTP_STATUS);
/* Check if a timestamp can be retrieved */
if (!(val & SYS_PTP_STATUS_PTP_MESS_VLD))
break;
WARN_ON(val & SYS_PTP_STATUS_PTP_OVFL);
/* Retrieve the ts ID and Tx port */
id = SYS_PTP_STATUS_PTP_MESS_ID_X(val);
txport = SYS_PTP_STATUS_PTP_MESS_TXPORT_X(val);
/* Retrieve its associated skb */
port = ocelot->ports[txport];
list_for_each_safe(pos, tmp, &port->skbs) {
entry = list_entry(pos, struct ocelot_skb, head);
if (entry->id != id)
continue;
skb = entry->skb;
list_del(pos);
kfree(entry);
}
/* Next ts */
ocelot_write(ocelot, SYS_PTP_NXT_PTP_NXT, SYS_PTP_NXT);
if (unlikely(!skb))
continue;
/* Get the h/w timestamp */
ocelot_get_hwtimestamp(ocelot, &ts);
/* Set the timestamp into the skb */
memset(&shhwtstamps, 0, sizeof(shhwtstamps));
shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
skb_tstamp_tx(skb, &shhwtstamps);
dev_kfree_skb_any(skb);
}
return IRQ_HANDLED;
}
static const struct of_device_id mscc_ocelot_match[] = {
{ .compatible = "mscc,vsc7514-switch" },
{ }
......@@ -172,12 +251,12 @@ MODULE_DEVICE_TABLE(of, mscc_ocelot_match);
static int mscc_ocelot_probe(struct platform_device *pdev)
{
int err, irq;
unsigned int i;
struct device_node *np = pdev->dev.of_node;
struct device_node *ports, *portnp;
int err, irq_xtr, irq_ptp_rdy;
struct ocelot *ocelot;
struct regmap *hsio;
unsigned int i;
u32 val;
struct {
......@@ -232,16 +311,29 @@ static int mscc_ocelot_probe(struct platform_device *pdev)
if (err)
return err;
irq = platform_get_irq_byname(pdev, "xtr");
if (irq < 0)
irq_xtr = platform_get_irq_byname(pdev, "xtr");
if (irq_xtr < 0)
return -ENODEV;
err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
err = devm_request_threaded_irq(&pdev->dev, irq_xtr, NULL,
ocelot_xtr_irq_handler, IRQF_ONESHOT,
"frame extraction", ocelot);
if (err)
return err;
irq_ptp_rdy = platform_get_irq_byname(pdev, "ptp_rdy");
if (irq_ptp_rdy > 0 && ocelot->targets[PTP]) {
err = devm_request_threaded_irq(&pdev->dev, irq_ptp_rdy, NULL,
ocelot_ptp_rdy_irq_handler,
IRQF_ONESHOT, "ptp ready",
ocelot);
if (err)
return err;
/* Both the PTP interrupt and the PTP bank are available */
ocelot->ptp = 1;
}
regmap_field_write(ocelot->regfields[SYS_RESET_CFG_MEM_INIT], 1);
regmap_field_write(ocelot->regfields[SYS_RESET_CFG_MEM_ENA], 1);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment