Commit 2fa8d3af authored by Brandon Streiff's avatar Brandon Streiff Committed by David S. Miller
Browse files

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: default avatarBrandon Streiff <brandon.streiff@ni.com>
Signed-off-by: default avatarRichard Cochran <richardcochran@gmail.com>
Signed-off-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 0d632c3d
Loading
Loading
Loading
Loading
+10 −0
Original line number Original line Diff line number Diff line
@@ -18,3 +18,13 @@ config NET_DSA_MV88E6XXX_GLOBAL2


	  It is required on most chips. If the chip you compile the support for
	  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.
	  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.
+1 −0
Original line number Original line Diff line number Diff line
@@ -8,4 +8,5 @@ mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2_avb.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2_avb.o
mv88e6xxx-objs += phy.o
mv88e6xxx-objs += phy.o
mv88e6xxx-objs += port.o
mv88e6xxx-objs += port.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
mv88e6xxx-objs += serdes.o
mv88e6xxx-objs += serdes.o
+20 −0
Original line number Original line Diff line number Diff line
@@ -38,6 +38,7 @@
#include "global2.h"
#include "global2.h"
#include "phy.h"
#include "phy.h"
#include "port.h"
#include "port.h"
#include "ptp.h"
#include "serdes.h"
#include "serdes.h"


static void assert_reg_lock(struct mv88e6xxx_chip *chip)
static void assert_reg_lock(struct mv88e6xxx_chip *chip)
@@ -2092,6 +2093,13 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
	if (err)
	if (err)
		goto unlock;
		goto unlock;


	/* Setup PTP Hardware Clock */
	if (chip->info->ptp_support) {
		err = mv88e6xxx_ptp_setup(chip);
		if (err)
			goto unlock;
	}

unlock:
unlock:
	mutex_unlock(&chip->reg_lock);
	mutex_unlock(&chip->reg_lock);


@@ -3484,6 +3492,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.pvt = true,
		.pvt = true,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_DSA,
		.tag_protocol = DSA_TAG_PROTO_DSA,
		.ptp_support = true,
		.ops = &mv88e6191_ops,
		.ops = &mv88e6191_ops,
	},
	},


@@ -3504,6 +3513,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.pvt = true,
		.pvt = true,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.ptp_support = true,
		.ops = &mv88e6240_ops,
		.ops = &mv88e6240_ops,
	},
	},


@@ -3524,6 +3534,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.pvt = true,
		.pvt = true,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_DSA,
		.tag_protocol = DSA_TAG_PROTO_DSA,
		.ptp_support = true,
		.ops = &mv88e6290_ops,
		.ops = &mv88e6290_ops,
	},
	},


@@ -3543,6 +3554,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.pvt = true,
		.pvt = true,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.ptp_support = true,
		.ops = &mv88e6320_ops,
		.ops = &mv88e6320_ops,
	},
	},


@@ -3561,6 +3573,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.atu_move_port_mask = 0xf,
		.atu_move_port_mask = 0xf,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.ptp_support = true,
		.ops = &mv88e6321_ops,
		.ops = &mv88e6321_ops,
	},
	},


@@ -3580,6 +3593,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.pvt = true,
		.pvt = true,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.ptp_support = true,
		.ops = &mv88e6341_ops,
		.ops = &mv88e6341_ops,
	},
	},


@@ -3640,6 +3654,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.pvt = true,
		.pvt = true,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.tag_protocol = DSA_TAG_PROTO_EDSA,
		.ptp_support = true,
		.ops = &mv88e6352_ops,
		.ops = &mv88e6352_ops,
	},
	},
	[MV88E6390] = {
	[MV88E6390] = {
@@ -3659,6 +3674,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.pvt = true,
		.pvt = true,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_DSA,
		.tag_protocol = DSA_TAG_PROTO_DSA,
		.ptp_support = true,
		.ops = &mv88e6390_ops,
		.ops = &mv88e6390_ops,
	},
	},
	[MV88E6390X] = {
	[MV88E6390X] = {
@@ -3678,6 +3694,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.pvt = true,
		.pvt = true,
		.multi_chip = true,
		.multi_chip = true,
		.tag_protocol = DSA_TAG_PROTO_DSA,
		.tag_protocol = DSA_TAG_PROTO_DSA,
		.ptp_support = true,
		.ops = &mv88e6390x_ops,
		.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 dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
	struct mv88e6xxx_chip *chip = ds->priv;
	struct mv88e6xxx_chip *chip = ds->priv;


	if (chip->info->ptp_support)
		mv88e6xxx_ptp_free(chip);

	mv88e6xxx_phy_destroy(chip);
	mv88e6xxx_phy_destroy(chip);
	mv88e6xxx_unregister_switch(chip);
	mv88e6xxx_unregister_switch(chip);
	mv88e6xxx_mdios_unregister(chip);
	mv88e6xxx_mdios_unregister(chip);
+15 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@
#include <linux/irq.h>
#include <linux/irq.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/consumer.h>
#include <linux/phy.h>
#include <linux/phy.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/timecounter.h>
#include <net/dsa.h>
#include <net/dsa.h>


#ifndef UINT64_MAX
#ifndef UINT64_MAX
@@ -126,6 +128,9 @@ struct mv88e6xxx_info {
	 */
	 */
	u8 atu_move_port_mask;
	u8 atu_move_port_mask;
	const struct mv88e6xxx_ops *ops;
	const struct mv88e6xxx_ops *ops;

	/* Supports PTP */
	bool ptp_support;
};
};


struct mv88e6xxx_atu_entry {
struct mv88e6xxx_atu_entry {
@@ -210,6 +215,16 @@ struct mv88e6xxx_chip {
	int watchdog_irq;
	int watchdog_irq;
	int atu_prob_irq;
	int atu_prob_irq;
	int vtu_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 {
struct mv88e6xxx_bus_ops {
+197 −0
Original line number Original line Diff line number Diff line
/*
 * Marvell 88E6xxx Switch PTP support
 *
 * Copyright (c) 2008 Marvell Semiconductor
 *
 * Copyright (c) 2017 National Instruments
 *      Erik Hons <erik.hons@ni.com>
 *      Brandon Streiff <brandon.streiff@ni.com>
 *      Dane Wagner <dane.wagner@ni.com>
 *
 * 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;
	}
}
Loading