From 2fa8d3af4badc40a39092140a01101119988faf6 Mon Sep 17 00:00:00 2001 From: Brandon Streiff Date: Wed, 14 Feb 2018 01:07:45 +0100 Subject: [PATCH] net: dsa: mv88e6xxx: expose switch time as a PTP hardware clock This patch adds basic support for exposing the 32-bit timestamp counter inside the mv88e6xxx switch as a ptp_clock. Adjfine implemented by Richard Cochran. Andrew Lunn: fix return value of PTP stub function. Signed-off-by: Brandon Streiff Signed-off-by: Richard Cochran Signed-off-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/dsa/mv88e6xxx/Kconfig | 10 ++ drivers/net/dsa/mv88e6xxx/Makefile | 1 + drivers/net/dsa/mv88e6xxx/chip.c | 20 +++ drivers/net/dsa/mv88e6xxx/chip.h | 15 +++ drivers/net/dsa/mv88e6xxx/ptp.c | 197 +++++++++++++++++++++++++++++ drivers/net/dsa/mv88e6xxx/ptp.h | 83 ++++++++++++ 6 files changed, 326 insertions(+) create mode 100644 drivers/net/dsa/mv88e6xxx/ptp.c create mode 100644 drivers/net/dsa/mv88e6xxx/ptp.h diff --git a/drivers/net/dsa/mv88e6xxx/Kconfig b/drivers/net/dsa/mv88e6xxx/Kconfig index 1aaa7a95ebc4..ae9e7f7cb31c 100644 --- a/drivers/net/dsa/mv88e6xxx/Kconfig +++ b/drivers/net/dsa/mv88e6xxx/Kconfig @@ -18,3 +18,13 @@ config NET_DSA_MV88E6XXX_GLOBAL2 It is required on most chips. If the chip you compile the support for doesn't have such registers set, say N here. In doubt, say Y. + +config NET_DSA_MV88E6XXX_PTP + bool "PTP support for Marvell 88E6xxx" + default n + depends on NET_DSA_MV88E6XXX_GLOBAL2 + imply NETWORK_PHY_TIMESTAMPING + imply PTP_1588_CLOCK + help + Say Y to enable PTP hardware timestamping on Marvell 88E6xxx switch + chips that support it. diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile index bdbbbf7b872e..40a423c9a982 100644 --- a/drivers/net/dsa/mv88e6xxx/Makefile +++ b/drivers/net/dsa/mv88e6xxx/Makefile @@ -8,4 +8,5 @@ mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2.o mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2_avb.o mv88e6xxx-objs += phy.o mv88e6xxx-objs += port.o +mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o mv88e6xxx-objs += serdes.o diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 413d50e606f2..a795efd41511 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -38,6 +38,7 @@ #include "global2.h" #include "phy.h" #include "port.h" +#include "ptp.h" #include "serdes.h" static void assert_reg_lock(struct mv88e6xxx_chip *chip) @@ -2092,6 +2093,13 @@ static int mv88e6xxx_setup(struct dsa_switch *ds) if (err) goto unlock; + /* Setup PTP Hardware Clock */ + if (chip->info->ptp_support) { + err = mv88e6xxx_ptp_setup(chip); + if (err) + goto unlock; + } + unlock: mutex_unlock(&chip->reg_lock); @@ -3484,6 +3492,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .pvt = true, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_DSA, + .ptp_support = true, .ops = &mv88e6191_ops, }, @@ -3504,6 +3513,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .pvt = true, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_EDSA, + .ptp_support = true, .ops = &mv88e6240_ops, }, @@ -3524,6 +3534,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .pvt = true, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_DSA, + .ptp_support = true, .ops = &mv88e6290_ops, }, @@ -3543,6 +3554,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .pvt = true, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_EDSA, + .ptp_support = true, .ops = &mv88e6320_ops, }, @@ -3561,6 +3573,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .atu_move_port_mask = 0xf, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_EDSA, + .ptp_support = true, .ops = &mv88e6321_ops, }, @@ -3580,6 +3593,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .pvt = true, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_EDSA, + .ptp_support = true, .ops = &mv88e6341_ops, }, @@ -3640,6 +3654,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .pvt = true, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_EDSA, + .ptp_support = true, .ops = &mv88e6352_ops, }, [MV88E6390] = { @@ -3659,6 +3674,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .pvt = true, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_DSA, + .ptp_support = true, .ops = &mv88e6390_ops, }, [MV88E6390X] = { @@ -3678,6 +3694,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .pvt = true, .multi_chip = true, .tag_protocol = DSA_TAG_PROTO_DSA, + .ptp_support = true, .ops = &mv88e6390x_ops, }, }; @@ -4031,6 +4048,9 @@ static void mv88e6xxx_remove(struct mdio_device *mdiodev) struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); struct mv88e6xxx_chip *chip = ds->priv; + if (chip->info->ptp_support) + mv88e6xxx_ptp_free(chip); + mv88e6xxx_phy_destroy(chip); mv88e6xxx_unregister_switch(chip); mv88e6xxx_mdios_unregister(chip); diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h index 5467c86f38a1..92e5fd6e2318 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #ifndef UINT64_MAX @@ -126,6 +128,9 @@ struct mv88e6xxx_info { */ u8 atu_move_port_mask; const struct mv88e6xxx_ops *ops; + + /* Supports PTP */ + bool ptp_support; }; struct mv88e6xxx_atu_entry { @@ -210,6 +215,16 @@ struct mv88e6xxx_chip { int watchdog_irq; int atu_prob_irq; int vtu_prob_irq; + + /* This cyclecounter abstracts the switch PTP time. + * reg_lock must be held for any operation that read()s. + */ + struct cyclecounter tstamp_cc; + struct timecounter tstamp_tc; + struct delayed_work overflow_work; + + struct ptp_clock *ptp_clock; + struct ptp_clock_info ptp_clock_info; }; struct mv88e6xxx_bus_ops { diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c new file mode 100644 index 000000000000..6bc643cea03d --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/ptp.c @@ -0,0 +1,197 @@ +/* + * Marvell 88E6xxx Switch PTP support + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2017 National Instruments + * Erik Hons + * Brandon Streiff + * Dane Wagner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "chip.h" +#include "global2.h" +#include "ptp.h" + +/* Raw timestamps are in units of 8-ns clock periods. */ +#define CC_SHIFT 28 +#define CC_MULT (8 << CC_SHIFT) +#define CC_MULT_NUM (1 << 9) +#define CC_MULT_DEM 15625ULL + +#define TAI_EVENT_WORK_INTERVAL msecs_to_jiffies(100) + +#define cc_to_chip(cc) container_of(cc, struct mv88e6xxx_chip, tstamp_cc) +#define ptp_to_chip(ptp) container_of(ptp, struct mv88e6xxx_chip, \ + ptp_clock_info) +#define dw_overflow_to_chip(dw) container_of(dw, struct mv88e6xxx_chip, \ + overflow_work) + +static int mv88e6xxx_tai_read(struct mv88e6xxx_chip *chip, int addr, + u16 *data, int len) +{ + if (!chip->info->ops->avb_ops->tai_read) + return -EOPNOTSUPP; + + return chip->info->ops->avb_ops->tai_read(chip, addr, data, len); +} + +static u64 mv88e6xxx_ptp_clock_read(const struct cyclecounter *cc) +{ + struct mv88e6xxx_chip *chip = cc_to_chip(cc); + u16 phc_time[2]; + int err; + + err = mv88e6xxx_tai_read(chip, MV88E6XXX_TAI_TIME_LO, phc_time, + ARRAY_SIZE(phc_time)); + if (err) + return 0; + else + return ((u32)phc_time[1] << 16) | phc_time[0]; +} + +static int mv88e6xxx_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct mv88e6xxx_chip *chip = ptp_to_chip(ptp); + int neg_adj = 0; + u32 diff, mult; + u64 adj; + + if (scaled_ppm < 0) { + neg_adj = 1; + scaled_ppm = -scaled_ppm; + } + mult = CC_MULT; + adj = CC_MULT_NUM; + adj *= scaled_ppm; + diff = div_u64(adj, CC_MULT_DEM); + + mutex_lock(&chip->reg_lock); + + timecounter_read(&chip->tstamp_tc); + chip->tstamp_cc.mult = neg_adj ? mult - diff : mult + diff; + + mutex_unlock(&chip->reg_lock); + + return 0; +} + +static int mv88e6xxx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct mv88e6xxx_chip *chip = ptp_to_chip(ptp); + + mutex_lock(&chip->reg_lock); + timecounter_adjtime(&chip->tstamp_tc, delta); + mutex_unlock(&chip->reg_lock); + + return 0; +} + +static int mv88e6xxx_ptp_gettime(struct ptp_clock_info *ptp, + struct timespec64 *ts) +{ + struct mv88e6xxx_chip *chip = ptp_to_chip(ptp); + u64 ns; + + mutex_lock(&chip->reg_lock); + ns = timecounter_read(&chip->tstamp_tc); + mutex_unlock(&chip->reg_lock); + + *ts = ns_to_timespec64(ns); + + return 0; +} + +static int mv88e6xxx_ptp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct mv88e6xxx_chip *chip = ptp_to_chip(ptp); + u64 ns; + + ns = timespec64_to_ns(ts); + + mutex_lock(&chip->reg_lock); + timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc, ns); + mutex_unlock(&chip->reg_lock); + + return 0; +} + +static int mv88e6xxx_ptp_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + return -EOPNOTSUPP; +} + +static int mv88e6xxx_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin, + enum ptp_pin_function func, unsigned int chan) +{ + return -EOPNOTSUPP; +} + +/* With a 125MHz input clock, the 32-bit timestamp counter overflows in ~34.3 + * seconds; this task forces periodic reads so that we don't miss any. + */ +#define MV88E6XXX_TAI_OVERFLOW_PERIOD (HZ * 16) +static void mv88e6xxx_ptp_overflow_check(struct work_struct *work) +{ + struct delayed_work *dw = to_delayed_work(work); + struct mv88e6xxx_chip *chip = dw_overflow_to_chip(dw); + struct timespec64 ts; + + mv88e6xxx_ptp_gettime(&chip->ptp_clock_info, &ts); + + schedule_delayed_work(&chip->overflow_work, + MV88E6XXX_TAI_OVERFLOW_PERIOD); +} + +int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip) +{ + /* Set up the cycle counter */ + memset(&chip->tstamp_cc, 0, sizeof(chip->tstamp_cc)); + chip->tstamp_cc.read = mv88e6xxx_ptp_clock_read; + chip->tstamp_cc.mask = CYCLECOUNTER_MASK(32); + chip->tstamp_cc.mult = CC_MULT; + chip->tstamp_cc.shift = CC_SHIFT; + + timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc, + ktime_to_ns(ktime_get_real())); + + INIT_DELAYED_WORK(&chip->overflow_work, mv88e6xxx_ptp_overflow_check); + + chip->ptp_clock_info.owner = THIS_MODULE; + snprintf(chip->ptp_clock_info.name, sizeof(chip->ptp_clock_info.name), + dev_name(chip->dev)); + chip->ptp_clock_info.max_adj = 1000000; + + chip->ptp_clock_info.adjfine = mv88e6xxx_ptp_adjfine; + chip->ptp_clock_info.adjtime = mv88e6xxx_ptp_adjtime; + chip->ptp_clock_info.gettime64 = mv88e6xxx_ptp_gettime; + chip->ptp_clock_info.settime64 = mv88e6xxx_ptp_settime; + chip->ptp_clock_info.enable = mv88e6xxx_ptp_enable; + chip->ptp_clock_info.verify = mv88e6xxx_ptp_verify; + + chip->ptp_clock = ptp_clock_register(&chip->ptp_clock_info, chip->dev); + if (IS_ERR(chip->ptp_clock)) + return PTR_ERR(chip->ptp_clock); + + schedule_delayed_work(&chip->overflow_work, + MV88E6XXX_TAI_OVERFLOW_PERIOD); + + return 0; +} + +void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip) +{ + if (chip->ptp_clock) { + cancel_delayed_work_sync(&chip->overflow_work); + + ptp_clock_unregister(chip->ptp_clock); + chip->ptp_clock = NULL; + } +} diff --git a/drivers/net/dsa/mv88e6xxx/ptp.h b/drivers/net/dsa/mv88e6xxx/ptp.h new file mode 100644 index 000000000000..5713d00800fc --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/ptp.h @@ -0,0 +1,83 @@ +/* + * Marvell 88E6xxx Switch PTP support + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2017 National Instruments + * Erik Hons + * Brandon Streiff + * Dane Wagner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _MV88E6XXX_PTP_H +#define _MV88E6XXX_PTP_H + +#include "chip.h" + +/* Offset 0x00: TAI Global Config */ +#define MV88E6XXX_TAI_CFG 0x00 + +/* Offset 0x01: Timestamp Clock Period (ps) */ +#define MV88E6XXX_TAI_CLOCK_PERIOD 0x01 + +/* Offset 0x02/0x03: Trigger Generation Amount */ +#define MV88E6XXX_TAI_TRIG_GEN_AMOUNT_LO 0x02 +#define MV88E6XXX_TAI_TRIG_GEN_AMOUNT_HI 0x03 + +/* Offset 0x04: Clock Compensation */ +#define MV88E6XXX_TAI_TRIG_CLOCK_COMP 0x04 + +/* Offset 0x05: Trigger Configuration */ +#define MV88E6XXX_TAI_TRIG_CFG 0x05 + +/* Offset 0x06: Ingress Rate Limiter Clock Generation Amount */ +#define MV88E6XXX_TAI_IRL_AMOUNT 0x06 + +/* Offset 0x07: Ingress Rate Limiter Compensation */ +#define MV88E6XXX_TAI_IRL_COMP 0x07 + +/* Offset 0x08: Ingress Rate Limiter Compensation */ +#define MV88E6XXX_TAI_IRL_COMP_PS 0x08 + +/* Offset 0x09: Event Status */ +#define MV88E6XXX_TAI_EVENT_STATUS 0x09 + +/* Offset 0x0A/0x0B: Event Time */ +#define MV88E6XXX_TAI_EVENT_TIME_LO 0x0a +#define MV88E6XXX_TAI_EVENT_TYPE_HI 0x0b + +/* Offset 0x0E/0x0F: PTP Global Time */ +#define MV88E6XXX_TAI_TIME_LO 0x0e +#define MV88E6XXX_TAI_TIME_HI 0x0f + +/* Offset 0x10/0x11: Trig Generation Time */ +#define MV88E6XXX_TAI_TRIG_TIME_LO 0x10 +#define MV88E6XXX_TAI_TRIG_TIME_HI 0x11 + +/* Offset 0x12: Lock Status */ +#define MV88E6XXX_TAI_LOCK_STATUS 0x12 + +#ifdef CONFIG_NET_DSA_MV88E6XXX_PTP + +int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip); +void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip); + +#else /* !CONFIG_NET_DSA_MV88E6XXX_PTP */ + +static inline int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip) +{ + return 0; +} + +static void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip) +{ +} + +#endif /* CONFIG_NET_DSA_MV88E6XXX_PTP */ + +#endif /* _MV88E6XXX_PTP_H */