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 58a4a0014e59..50de304abe2f 100644 --- a/drivers/net/dsa/mv88e6xxx/Makefile +++ b/drivers/net/dsa/mv88e6xxx/Makefile @@ -5,6 +5,10 @@ mv88e6xxx-objs += global1.o mv88e6xxx-objs += global1_atu.o mv88e6xxx-objs += global1_vtu.o mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2.o +mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2_avb.o +mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2_scratch.o +mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += hwtstamp.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 eb328bade225..af63710e93c1 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -36,8 +36,10 @@ #include "chip.h" #include "global1.h" #include "global2.h" +#include "hwtstamp.h" #include "phy.h" #include "port.h" +#include "ptp.h" #include "serdes.h" static void assert_reg_lock(struct mv88e6xxx_chip *chip) @@ -2092,6 +2094,17 @@ static int mv88e6xxx_setup(struct dsa_switch *ds) if (err) goto unlock; + /* Setup PTP Hardware Clock and timestamping */ + if (chip->info->ptp_support) { + err = mv88e6xxx_ptp_setup(chip); + if (err) + goto unlock; + + err = mv88e6xxx_hwtstamp_setup(chip); + if (err) + goto unlock; + } + unlock: mutex_unlock(&chip->reg_lock); @@ -2472,6 +2485,7 @@ static const struct mv88e6xxx_ops mv88e6141_ops = { .reset = mv88e6352_g1_reset, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, + .gpio_ops = &mv88e6352_gpio_ops, }; static const struct mv88e6xxx_ops mv88e6161_ops = { @@ -2602,6 +2616,7 @@ static const struct mv88e6xxx_ops mv88e6172_ops = { .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .serdes_power = mv88e6352_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, }; static const struct mv88e6xxx_ops mv88e6175_ops = { @@ -2673,6 +2688,7 @@ static const struct mv88e6xxx_ops mv88e6176_ops = { .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .serdes_power = mv88e6352_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, }; static const struct mv88e6xxx_ops mv88e6185_ops = { @@ -2736,6 +2752,7 @@ static const struct mv88e6xxx_ops mv88e6190_ops = { .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .serdes_power = mv88e6390_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, }; static const struct mv88e6xxx_ops mv88e6190x_ops = { @@ -2771,6 +2788,7 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = { .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .serdes_power = mv88e6390_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, }; static const struct mv88e6xxx_ops mv88e6191_ops = { @@ -2843,6 +2861,8 @@ static const struct mv88e6xxx_ops mv88e6240_ops = { .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .serdes_power = mv88e6352_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, + .avb_ops = &mv88e6352_avb_ops, }; static const struct mv88e6xxx_ops mv88e6290_ops = { @@ -2879,6 +2899,8 @@ static const struct mv88e6xxx_ops mv88e6290_ops = { .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .serdes_power = mv88e6390_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, + .avb_ops = &mv88e6390_avb_ops, }; static const struct mv88e6xxx_ops mv88e6320_ops = { @@ -2913,6 +2935,8 @@ static const struct mv88e6xxx_ops mv88e6320_ops = { .reset = mv88e6352_g1_reset, .vtu_getnext = mv88e6185_g1_vtu_getnext, .vtu_loadpurge = mv88e6185_g1_vtu_loadpurge, + .gpio_ops = &mv88e6352_gpio_ops, + .avb_ops = &mv88e6352_avb_ops, }; static const struct mv88e6xxx_ops mv88e6321_ops = { @@ -2945,6 +2969,8 @@ static const struct mv88e6xxx_ops mv88e6321_ops = { .reset = mv88e6352_g1_reset, .vtu_getnext = mv88e6185_g1_vtu_getnext, .vtu_loadpurge = mv88e6185_g1_vtu_loadpurge, + .gpio_ops = &mv88e6352_gpio_ops, + .avb_ops = &mv88e6352_avb_ops, }; static const struct mv88e6xxx_ops mv88e6341_ops = { @@ -2981,6 +3007,8 @@ static const struct mv88e6xxx_ops mv88e6341_ops = { .reset = mv88e6352_g1_reset, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, + .gpio_ops = &mv88e6352_gpio_ops, + .avb_ops = &mv88e6390_avb_ops, }; static const struct mv88e6xxx_ops mv88e6350_ops = { @@ -3049,6 +3077,7 @@ static const struct mv88e6xxx_ops mv88e6351_ops = { .reset = mv88e6352_g1_reset, .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, + .avb_ops = &mv88e6352_avb_ops, }; static const struct mv88e6xxx_ops mv88e6352_ops = { @@ -3086,6 +3115,8 @@ static const struct mv88e6xxx_ops mv88e6352_ops = { .vtu_getnext = mv88e6352_g1_vtu_getnext, .vtu_loadpurge = mv88e6352_g1_vtu_loadpurge, .serdes_power = mv88e6352_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, + .avb_ops = &mv88e6352_avb_ops, }; static const struct mv88e6xxx_ops mv88e6390_ops = { @@ -3124,6 +3155,8 @@ static const struct mv88e6xxx_ops mv88e6390_ops = { .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .serdes_power = mv88e6390_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, + .avb_ops = &mv88e6390_avb_ops, }; static const struct mv88e6xxx_ops mv88e6390x_ops = { @@ -3162,6 +3195,8 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = { .vtu_getnext = mv88e6390_g1_vtu_getnext, .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, .serdes_power = mv88e6390_serdes_power, + .gpio_ops = &mv88e6352_gpio_ops, + .avb_ops = &mv88e6390_avb_ops, }; static const struct mv88e6xxx_info mv88e6xxx_table[] = { @@ -3267,6 +3302,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6341", .num_databases = 4096, .num_ports = 6, + .num_gpio = 11, .max_vid = 4095, .port_base_addr = 0x10, .global1_addr = 0x1b, @@ -3346,6 +3382,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6172", .num_databases = 4096, .num_ports = 7, + .num_gpio = 15, .max_vid = 4095, .port_base_addr = 0x10, .global1_addr = 0x1b, @@ -3386,6 +3423,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6176", .num_databases = 4096, .num_ports = 7, + .num_gpio = 15, .max_vid = 4095, .port_base_addr = 0x10, .global1_addr = 0x1b, @@ -3424,6 +3462,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6190", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ + .num_gpio = 16, .max_vid = 8191, .port_base_addr = 0x0, .global1_addr = 0x1b, @@ -3444,6 +3483,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6190X", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ + .num_gpio = 16, .max_vid = 8191, .port_base_addr = 0x0, .global1_addr = 0x1b, @@ -3475,6 +3515,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, }, @@ -3484,6 +3525,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6240", .num_databases = 4096, .num_ports = 7, + .num_gpio = 15, .max_vid = 4095, .port_base_addr = 0x10, .global1_addr = 0x1b, @@ -3495,6 +3537,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, }, @@ -3504,6 +3547,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6290", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ + .num_gpio = 16, .max_vid = 8191, .port_base_addr = 0x0, .global1_addr = 0x1b, @@ -3515,6 +3559,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, }, @@ -3524,6 +3569,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6320", .num_databases = 4096, .num_ports = 7, + .num_gpio = 15, .max_vid = 4095, .port_base_addr = 0x10, .global1_addr = 0x1b, @@ -3534,6 +3580,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, }, @@ -3543,6 +3590,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6321", .num_databases = 4096, .num_ports = 7, + .num_gpio = 15, .max_vid = 4095, .port_base_addr = 0x10, .global1_addr = 0x1b, @@ -3552,6 +3600,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, }, @@ -3561,6 +3610,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6341", .num_databases = 4096, .num_ports = 6, + .num_gpio = 11, .max_vid = 4095, .port_base_addr = 0x10, .global1_addr = 0x1b, @@ -3571,6 +3621,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, }, @@ -3620,6 +3671,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6352", .num_databases = 4096, .num_ports = 7, + .num_gpio = 15, .max_vid = 4095, .port_base_addr = 0x10, .global1_addr = 0x1b, @@ -3631,6 +3683,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] = { @@ -3639,6 +3692,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6390", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ + .num_gpio = 16, .max_vid = 8191, .port_base_addr = 0x0, .global1_addr = 0x1b, @@ -3650,6 +3704,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] = { @@ -3658,6 +3713,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .name = "Marvell 88E6390X", .num_databases = 4096, .num_ports = 11, /* 10 + Z80 */ + .num_gpio = 16, .max_vid = 8191, .port_base_addr = 0x0, .global1_addr = 0x1b, @@ -3669,6 +3725,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, }, }; @@ -3880,6 +3937,11 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = { .port_mdb_del = mv88e6xxx_port_mdb_del, .crosschip_bridge_join = mv88e6xxx_crosschip_bridge_join, .crosschip_bridge_leave = mv88e6xxx_crosschip_bridge_leave, + .port_hwtstamp_set = mv88e6xxx_port_hwtstamp_set, + .port_hwtstamp_get = mv88e6xxx_port_hwtstamp_get, + .port_txtstamp = mv88e6xxx_port_txtstamp, + .port_rxtstamp = mv88e6xxx_port_rxtstamp, + .get_ts_info = mv88e6xxx_get_ts_info, }; static struct dsa_switch_driver mv88e6xxx_switch_drv = { @@ -4022,6 +4084,11 @@ 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_hwtstamp_free(chip); + 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 3dba6e90adcf..97d7915f32c7 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 @@ -39,6 +41,8 @@ #define MV88E6XXX_MAX_PVT_SWITCHES 32 #define MV88E6XXX_MAX_PVT_PORTS 16 +#define MV88E6XXX_MAX_GPIO 16 + enum mv88e6xxx_egress_mode { MV88E6XXX_EGRESS_MODE_UNMODIFIED, MV88E6XXX_EGRESS_MODE_UNTAGGED, @@ -105,6 +109,7 @@ struct mv88e6xxx_info { const char *name; unsigned int num_databases; unsigned int num_ports; + unsigned int num_gpio; unsigned int max_vid; unsigned int port_base_addr; unsigned int global1_addr; @@ -126,6 +131,9 @@ struct mv88e6xxx_info { */ u8 atu_move_port_mask; const struct mv88e6xxx_ops *ops; + + /* Supports PTP */ + bool ptp_support; }; struct mv88e6xxx_atu_entry { @@ -146,6 +154,8 @@ struct mv88e6xxx_vtu_entry { struct mv88e6xxx_bus_ops; struct mv88e6xxx_irq_ops; +struct mv88e6xxx_gpio_ops; +struct mv88e6xxx_avb_ops; struct mv88e6xxx_irq { u16 masked; @@ -154,6 +164,32 @@ struct mv88e6xxx_irq { unsigned int nirqs; }; +/* state flags for mv88e6xxx_port_hwtstamp::state */ +enum { + MV88E6XXX_HWTSTAMP_ENABLED, + MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, +}; + +struct mv88e6xxx_port_hwtstamp { + /* Port index */ + int port_id; + + /* Timestamping state */ + unsigned long state; + + /* Resources for receive timestamping */ + struct sk_buff_head rx_queue; + struct sk_buff_head rx_queue2; + + /* Resources for transmit timestamping */ + unsigned long tx_tstamp_start; + struct sk_buff *tx_skb; + u16 tx_seq_id; + + /* Current timestamp configuration */ + struct hwtstamp_config tstamp_config; +}; + struct mv88e6xxx_chip { const struct mv88e6xxx_info *info; @@ -209,6 +245,26 @@ struct mv88e6xxx_chip { int watchdog_irq; int atu_prob_irq; int vtu_prob_irq; + + /* GPIO resources */ + u8 gpio_data[2]; + + /* 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 delayed_work tai_event_work; + struct ptp_pin_desc pin_config[MV88E6XXX_MAX_GPIO]; + u16 trig_config; + u16 evcap_config; + + /* Per-port timestamping resources. */ + struct mv88e6xxx_port_hwtstamp port_hwtstamp[DSA_MAX_PORTS]; }; struct mv88e6xxx_bus_ops { @@ -344,6 +400,12 @@ struct mv88e6xxx_ops { struct mv88e6xxx_vtu_entry *entry); int (*vtu_loadpurge)(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry); + + /* GPIO operations */ + const struct mv88e6xxx_gpio_ops *gpio_ops; + + /* Interface to the AVB/PTP registers */ + const struct mv88e6xxx_avb_ops *avb_ops; }; struct mv88e6xxx_irq_ops { @@ -355,6 +417,42 @@ struct mv88e6xxx_irq_ops { void (*irq_free)(struct mv88e6xxx_chip *chip); }; +struct mv88e6xxx_gpio_ops { + /* Get/set data on GPIO pin */ + int (*get_data)(struct mv88e6xxx_chip *chip, unsigned int pin); + int (*set_data)(struct mv88e6xxx_chip *chip, unsigned int pin, + int value); + + /* get/set GPIO direction */ + int (*get_dir)(struct mv88e6xxx_chip *chip, unsigned int pin); + int (*set_dir)(struct mv88e6xxx_chip *chip, unsigned int pin, + bool input); + + /* get/set GPIO pin control */ + int (*get_pctl)(struct mv88e6xxx_chip *chip, unsigned int pin, + int *func); + int (*set_pctl)(struct mv88e6xxx_chip *chip, unsigned int pin, + int func); +}; + +struct mv88e6xxx_avb_ops { + /* Access port-scoped Precision Time Protocol registers */ + int (*port_ptp_read)(struct mv88e6xxx_chip *chip, int port, int addr, + u16 *data, int len); + int (*port_ptp_write)(struct mv88e6xxx_chip *chip, int port, int addr, + u16 data); + + /* Access global Precision Time Protocol registers */ + int (*ptp_read)(struct mv88e6xxx_chip *chip, int addr, u16 *data, + int len); + int (*ptp_write)(struct mv88e6xxx_chip *chip, int addr, u16 data); + + /* Access global Time Application Interface registers */ + int (*tai_read)(struct mv88e6xxx_chip *chip, int addr, u16 *data, + int len); + int (*tai_write)(struct mv88e6xxx_chip *chip, int addr, u16 data); +}; + #define STATS_TYPE_PORT BIT(0) #define STATS_TYPE_BANK0 BIT(1) #define STATS_TYPE_BANK1 BIT(2) @@ -386,6 +484,11 @@ static inline u16 mv88e6xxx_port_mask(struct mv88e6xxx_chip *chip) return GENMASK(mv88e6xxx_num_ports(chip) - 1, 0); } +static inline unsigned int mv88e6xxx_num_gpio(struct mv88e6xxx_chip *chip) +{ + return chip->info->num_gpio; +} + int mv88e6xxx_read(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val); int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val); int mv88e6xxx_update(struct mv88e6xxx_chip *chip, int addr, int reg, diff --git a/drivers/net/dsa/mv88e6xxx/global2.c b/drivers/net/dsa/mv88e6xxx/global2.c index af0727877825..5f370f1fc7c4 100644 --- a/drivers/net/dsa/mv88e6xxx/global2.c +++ b/drivers/net/dsa/mv88e6xxx/global2.c @@ -20,22 +20,22 @@ #include "global1.h" /* for MV88E6XXX_G1_STS_IRQ_DEVICE */ #include "global2.h" -static int mv88e6xxx_g2_read(struct mv88e6xxx_chip *chip, int reg, u16 *val) +int mv88e6xxx_g2_read(struct mv88e6xxx_chip *chip, int reg, u16 *val) { return mv88e6xxx_read(chip, chip->info->global2_addr, reg, val); } -static int mv88e6xxx_g2_write(struct mv88e6xxx_chip *chip, int reg, u16 val) +int mv88e6xxx_g2_write(struct mv88e6xxx_chip *chip, int reg, u16 val) { return mv88e6xxx_write(chip, chip->info->global2_addr, reg, val); } -static int mv88e6xxx_g2_update(struct mv88e6xxx_chip *chip, int reg, u16 update) +int mv88e6xxx_g2_update(struct mv88e6xxx_chip *chip, int reg, u16 update) { return mv88e6xxx_update(chip, chip->info->global2_addr, reg, update); } -static int mv88e6xxx_g2_wait(struct mv88e6xxx_chip *chip, int reg, u16 mask) +int mv88e6xxx_g2_wait(struct mv88e6xxx_chip *chip, int reg, u16 mask) { return mv88e6xxx_wait(chip, chip->info->global2_addr, reg, mask); } @@ -798,6 +798,7 @@ int mv88e6xxx_g2_smi_phy_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus, val); } +/* Offset 0x1B: Watchdog Control */ static int mv88e6097_watchdog_action(struct mv88e6xxx_chip *chip, int irq) { u16 reg; diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h index 669f59017b12..25f92b3d7157 100644 --- a/drivers/net/dsa/mv88e6xxx/global2.h +++ b/drivers/net/dsa/mv88e6xxx/global2.h @@ -149,7 +149,26 @@ #define MV88E6390_G2_EEPROM_ADDR_MASK 0xffff /* Offset 0x16: AVB Command Register */ -#define MV88E6352_G2_AVB_CMD 0x16 +#define MV88E6352_G2_AVB_CMD 0x16 +#define MV88E6352_G2_AVB_CMD_BUSY 0x8000 +#define MV88E6352_G2_AVB_CMD_OP_READ 0x4000 +#define MV88E6352_G2_AVB_CMD_OP_READ_INCR 0x6000 +#define MV88E6352_G2_AVB_CMD_OP_WRITE 0x3000 +#define MV88E6390_G2_AVB_CMD_OP_READ 0x0000 +#define MV88E6390_G2_AVB_CMD_OP_READ_INCR 0x4000 +#define MV88E6390_G2_AVB_CMD_OP_WRITE 0x6000 +#define MV88E6352_G2_AVB_CMD_PORT_MASK 0x0f00 +#define MV88E6352_G2_AVB_CMD_PORT_TAIGLOBAL 0xe +#define MV88E6352_G2_AVB_CMD_PORT_PTPGLOBAL 0xf +#define MV88E6390_G2_AVB_CMD_PORT_MASK 0x1f00 +#define MV88E6390_G2_AVB_CMD_PORT_TAIGLOBAL 0x1e +#define MV88E6390_G2_AVB_CMD_PORT_PTPGLOBAL 0x1f +#define MV88E6352_G2_AVB_CMD_BLOCK_PTP 0 +#define MV88E6352_G2_AVB_CMD_BLOCK_AVB 1 +#define MV88E6352_G2_AVB_CMD_BLOCK_QAV 2 +#define MV88E6352_G2_AVB_CMD_BLOCK_QVB 3 +#define MV88E6352_G2_AVB_CMD_BLOCK_MASK 0x00e0 +#define MV88E6352_G2_AVB_CMD_ADDR_MASK 0x001f /* Offset 0x17: AVB Data Register */ #define MV88E6352_G2_AVB_DATA 0x17 @@ -223,6 +242,35 @@ #define MV88E6352_G2_NOEGR_POLICY 0x2000 #define MV88E6390_G2_LAG_ID_4 0x2000 +/* Scratch/Misc registers accessed through MV88E6XXX_G2_SCRATCH_MISC */ +/* Offset 0x02: Misc Configuration */ +#define MV88E6352_G2_SCRATCH_MISC_CFG 0x02 +#define MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI 0x80 +/* Offset 0x60-0x61: GPIO Configuration */ +#define MV88E6352_G2_SCRATCH_GPIO_CFG0 0x60 +#define MV88E6352_G2_SCRATCH_GPIO_CFG1 0x61 +/* Offset 0x62-0x63: GPIO Direction */ +#define MV88E6352_G2_SCRATCH_GPIO_DIR0 0x62 +#define MV88E6352_G2_SCRATCH_GPIO_DIR1 0x63 +#define MV88E6352_G2_SCRATCH_GPIO_DIR_OUT 0 +#define MV88E6352_G2_SCRATCH_GPIO_DIR_IN 1 +/* Offset 0x64-0x65: GPIO Data */ +#define MV88E6352_G2_SCRATCH_GPIO_DATA0 0x64 +#define MV88E6352_G2_SCRATCH_GPIO_DATA1 0x65 +/* Offset 0x68-0x6F: GPIO Pin Control */ +#define MV88E6352_G2_SCRATCH_GPIO_PCTL0 0x68 +#define MV88E6352_G2_SCRATCH_GPIO_PCTL1 0x69 +#define MV88E6352_G2_SCRATCH_GPIO_PCTL2 0x6A +#define MV88E6352_G2_SCRATCH_GPIO_PCTL3 0x6B +#define MV88E6352_G2_SCRATCH_GPIO_PCTL4 0x6C +#define MV88E6352_G2_SCRATCH_GPIO_PCTL5 0x6D +#define MV88E6352_G2_SCRATCH_GPIO_PCTL6 0x6E +#define MV88E6352_G2_SCRATCH_GPIO_PCTL7 0x6F + +#define MV88E6352_G2_SCRATCH_GPIO_PCTL_GPIO 0 +#define MV88E6352_G2_SCRATCH_GPIO_PCTL_TRIG 1 +#define MV88E6352_G2_SCRATCH_GPIO_PCTL_EVREQ 2 + #ifdef CONFIG_NET_DSA_MV88E6XXX_GLOBAL2 static inline int mv88e6xxx_g2_require(struct mv88e6xxx_chip *chip) @@ -230,6 +278,11 @@ static inline int mv88e6xxx_g2_require(struct mv88e6xxx_chip *chip) return 0; } +int mv88e6xxx_g2_read(struct mv88e6xxx_chip *chip, int reg, u16 *val); +int mv88e6xxx_g2_write(struct mv88e6xxx_chip *chip, int reg, u16 val); +int mv88e6xxx_g2_update(struct mv88e6xxx_chip *chip, int reg, u16 update); +int mv88e6xxx_g2_wait(struct mv88e6xxx_chip *chip, int reg, u16 mask); + int mv88e6352_g2_irl_init_all(struct mv88e6xxx_chip *chip, int port); int mv88e6390_g2_irl_init_all(struct mv88e6xxx_chip *chip, int port); @@ -267,6 +320,11 @@ int mv88e6xxx_g2_pot_clear(struct mv88e6xxx_chip *chip); extern const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops; extern const struct mv88e6xxx_irq_ops mv88e6390_watchdog_ops; +extern const struct mv88e6xxx_avb_ops mv88e6352_avb_ops; +extern const struct mv88e6xxx_avb_ops mv88e6390_avb_ops; + +extern const struct mv88e6xxx_gpio_ops mv88e6352_gpio_ops; + #else /* !CONFIG_NET_DSA_MV88E6XXX_GLOBAL2 */ static inline int mv88e6xxx_g2_require(struct mv88e6xxx_chip *chip) @@ -279,6 +337,26 @@ static inline int mv88e6xxx_g2_require(struct mv88e6xxx_chip *chip) return 0; } +static int mv88e6xxx_g2_read(struct mv88e6xxx_chip *chip, int reg, u16 *val) +{ + return -EOPNOTSUPP; +} + +static int mv88e6xxx_g2_write(struct mv88e6xxx_chip *chip, int reg, u16 val) +{ + return -EOPNOTSUPP; +} + +static int mv88e6xxx_g2_update(struct mv88e6xxx_chip *chip, int reg, u16 update) +{ + return -EOPNOTSUPP; +} + +static int mv88e6xxx_g2_wait(struct mv88e6xxx_chip *chip, int reg, u16 mask) +{ + return -EOPNOTSUPP; +} + static inline int mv88e6352_g2_irl_init_all(struct mv88e6xxx_chip *chip, int port) { @@ -382,6 +460,11 @@ static inline int mv88e6xxx_g2_pot_clear(struct mv88e6xxx_chip *chip) static const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops = {}; static const struct mv88e6xxx_irq_ops mv88e6390_watchdog_ops = {}; +static const struct mv88e6xxx_avb_ops mv88e6352_avb_ops = {}; +static const struct mv88e6xxx_avb_ops mv88e6390_avb_ops = {}; + +static const struct mv88e6xxx_gpio_ops mv88e6352_gpio_ops = {}; + #endif /* CONFIG_NET_DSA_MV88E6XXX_GLOBAL2 */ #endif /* _MV88E6XXX_GLOBAL2_H */ diff --git a/drivers/net/dsa/mv88e6xxx/global2_avb.c b/drivers/net/dsa/mv88e6xxx/global2_avb.c new file mode 100644 index 000000000000..2e398ccb88ca --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/global2_avb.c @@ -0,0 +1,193 @@ +/* + * Marvell 88E6xxx Switch Global 2 Registers support + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2016-2017 Savoir-faire Linux Inc. + * Vivien Didelot + * + * Copyright (c) 2017 National Instruments + * Brandon Streiff + * + * 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 "global2.h" + +/* Offset 0x16: AVB Command Register + * Offset 0x17: AVB Data Register + * + * There are two different versions of this register interface: + * "6352": 3-bit "op" field, 4-bit "port" field. + * "6390": 2-bit "op" field, 5-bit "port" field. + * + * The "op" codes are different between the two, as well as the special + * port fields for global PTP and TAI configuration. + */ + +/* mv88e6xxx_g2_avb_read -- Read one or multiple 16-bit words. + * The hardware supports snapshotting up to four contiguous registers. + */ +static int mv88e6xxx_g2_avb_read(struct mv88e6xxx_chip *chip, u16 readop, + u16 *data, int len) +{ + int err; + int i; + + /* Hardware can only snapshot four words. */ + if (len > 4) + return -E2BIG; + + err = mv88e6xxx_g2_update(chip, MV88E6352_G2_AVB_CMD, readop); + if (err) + return err; + + for (i = 0; i < len; ++i) { + err = mv88e6xxx_g2_read(chip, MV88E6352_G2_AVB_DATA, + &data[i]); + if (err) + return err; + } + + return 0; +} + +/* mv88e6xxx_g2_avb_write -- Write one 16-bit word. */ +static int mv88e6xxx_g2_avb_write(struct mv88e6xxx_chip *chip, u16 writeop, + u16 data) +{ + int err; + + err = mv88e6xxx_g2_write(chip, MV88E6352_G2_AVB_DATA, data); + if (err) + return err; + + return mv88e6xxx_g2_update(chip, MV88E6352_G2_AVB_CMD, writeop); +} + +static int mv88e6352_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip, + int port, int addr, u16 *data, + int len) +{ + u16 readop = (len == 1 ? MV88E6352_G2_AVB_CMD_OP_READ : + MV88E6352_G2_AVB_CMD_OP_READ_INCR) | + (port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) | + addr; + + return mv88e6xxx_g2_avb_read(chip, readop, data, len); +} + +static int mv88e6352_g2_avb_port_ptp_write(struct mv88e6xxx_chip *chip, + int port, int addr, u16 data) +{ + u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE | (port << 8) | + (MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) | addr; + + return mv88e6xxx_g2_avb_write(chip, writeop, data); +} + +static int mv88e6352_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr, + u16 *data, int len) +{ + return mv88e6352_g2_avb_port_ptp_read(chip, + MV88E6352_G2_AVB_CMD_PORT_PTPGLOBAL, + addr, data, len); +} + +static int mv88e6352_g2_avb_ptp_write(struct mv88e6xxx_chip *chip, int addr, + u16 data) +{ + return mv88e6352_g2_avb_port_ptp_write(chip, + MV88E6352_G2_AVB_CMD_PORT_PTPGLOBAL, + addr, data); +} + +static int mv88e6352_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr, + u16 *data, int len) +{ + return mv88e6352_g2_avb_port_ptp_read(chip, + MV88E6352_G2_AVB_CMD_PORT_TAIGLOBAL, + addr, data, len); +} + +static int mv88e6352_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr, + u16 data) +{ + return mv88e6352_g2_avb_port_ptp_write(chip, + MV88E6352_G2_AVB_CMD_PORT_TAIGLOBAL, + addr, data); +} + +const struct mv88e6xxx_avb_ops mv88e6352_avb_ops = { + .port_ptp_read = mv88e6352_g2_avb_port_ptp_read, + .port_ptp_write = mv88e6352_g2_avb_port_ptp_write, + .ptp_read = mv88e6352_g2_avb_ptp_read, + .ptp_write = mv88e6352_g2_avb_ptp_write, + .tai_read = mv88e6352_g2_avb_tai_read, + .tai_write = mv88e6352_g2_avb_tai_write, +}; + +static int mv88e6390_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip, + int port, int addr, u16 *data, + int len) +{ + u16 readop = (len == 1 ? MV88E6390_G2_AVB_CMD_OP_READ : + MV88E6390_G2_AVB_CMD_OP_READ_INCR) | + (port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) | + addr; + + return mv88e6xxx_g2_avb_read(chip, readop, data, len); +} + +static int mv88e6390_g2_avb_port_ptp_write(struct mv88e6xxx_chip *chip, + int port, int addr, u16 data) +{ + u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE | (port << 8) | + (MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) | addr; + + return mv88e6xxx_g2_avb_write(chip, writeop, data); +} + +static int mv88e6390_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr, + u16 *data, int len) +{ + return mv88e6390_g2_avb_port_ptp_read(chip, + MV88E6390_G2_AVB_CMD_PORT_PTPGLOBAL, + addr, data, len); +} + +static int mv88e6390_g2_avb_ptp_write(struct mv88e6xxx_chip *chip, int addr, + u16 data) +{ + return mv88e6390_g2_avb_port_ptp_write(chip, + MV88E6390_G2_AVB_CMD_PORT_PTPGLOBAL, + addr, data); +} + +static int mv88e6390_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr, + u16 *data, int len) +{ + return mv88e6390_g2_avb_port_ptp_read(chip, + MV88E6390_G2_AVB_CMD_PORT_TAIGLOBAL, + addr, data, len); +} + +static int mv88e6390_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr, + u16 data) +{ + return mv88e6390_g2_avb_port_ptp_write(chip, + MV88E6390_G2_AVB_CMD_PORT_TAIGLOBAL, + addr, data); +} + +const struct mv88e6xxx_avb_ops mv88e6390_avb_ops = { + .port_ptp_read = mv88e6390_g2_avb_port_ptp_read, + .port_ptp_write = mv88e6390_g2_avb_port_ptp_write, + .ptp_read = mv88e6390_g2_avb_ptp_read, + .ptp_write = mv88e6390_g2_avb_ptp_write, + .tai_read = mv88e6390_g2_avb_tai_read, + .tai_write = mv88e6390_g2_avb_tai_write, +}; diff --git a/drivers/net/dsa/mv88e6xxx/global2_scratch.c b/drivers/net/dsa/mv88e6xxx/global2_scratch.c new file mode 100644 index 000000000000..0ff12bff9f0e --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/global2_scratch.c @@ -0,0 +1,240 @@ +/* + * Marvell 88E6xxx Switch Global 2 Scratch & Misc Registers support + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2017 National Instruments + * Brandon Streiff + * + * 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" + +/* Offset 0x1A: Scratch and Misc. Register */ +static int mv88e6xxx_g2_scratch_read(struct mv88e6xxx_chip *chip, int reg, + u8 *data) +{ + u16 value; + int err; + + err = mv88e6xxx_g2_write(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC, + reg << 8); + if (err) + return err; + + err = mv88e6xxx_g2_read(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC, &value); + if (err) + return err; + + *data = (value & MV88E6XXX_G2_SCRATCH_MISC_DATA_MASK); + + return 0; +} + +static int mv88e6xxx_g2_scratch_write(struct mv88e6xxx_chip *chip, int reg, + u8 data) +{ + u16 value = (reg << 8) | data; + + return mv88e6xxx_g2_update(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC, value); +} + +/** + * mv88e6xxx_g2_scratch_gpio_get_bit - get a bit + * @chip: chip private data + * @nr: bit index + * @set: is bit set? + */ +static int mv88e6xxx_g2_scratch_get_bit(struct mv88e6xxx_chip *chip, + int base_reg, unsigned int offset, + int *set) +{ + int reg = base_reg + (offset / 8); + u8 mask = (1 << (offset & 0x7)); + u8 val; + int err; + + err = mv88e6xxx_g2_scratch_read(chip, reg, &val); + if (err) + return err; + + *set = !!(mask & val); + + return 0; +} + +/** + * mv88e6xxx_g2_scratch_gpio_set_bit - set (or clear) a bit + * @chip: chip private data + * @nr: bit index + * @set: set if true, clear if false + * + * Helper function for dealing with the direction and data registers. + */ +static int mv88e6xxx_g2_scratch_set_bit(struct mv88e6xxx_chip *chip, + int base_reg, unsigned int offset, + int set) +{ + int reg = base_reg + (offset / 8); + u8 mask = (1 << (offset & 0x7)); + u8 val; + int err; + + err = mv88e6xxx_g2_scratch_read(chip, reg, &val); + if (err) + return err; + + if (set) + val |= mask; + else + val &= ~mask; + + return mv88e6xxx_g2_scratch_write(chip, reg, val); +} + +/** + * mv88e6352_g2_scratch_gpio_get_data - get data on gpio pin + * @chip: chip private data + * @pin: gpio index + * + * Return: 0 for low, 1 for high, negative error + */ +static int mv88e6352_g2_scratch_gpio_get_data(struct mv88e6xxx_chip *chip, + unsigned int pin) +{ + int val = 0; + int err; + + err = mv88e6xxx_g2_scratch_get_bit(chip, + MV88E6352_G2_SCRATCH_GPIO_DATA0, + pin, &val); + if (err) + return err; + + return val; +} + +/** + * mv88e6352_g2_scratch_gpio_set_data - set data on gpio pin + * @chip: chip private data + * @pin: gpio index + * @value: value to set + */ +static int mv88e6352_g2_scratch_gpio_set_data(struct mv88e6xxx_chip *chip, + unsigned int pin, int value) +{ + u8 mask = (1 << (pin & 0x7)); + int offset = (pin / 8); + int reg; + + reg = MV88E6352_G2_SCRATCH_GPIO_DATA0 + offset; + + if (value) + chip->gpio_data[offset] |= mask; + else + chip->gpio_data[offset] &= ~mask; + + return mv88e6xxx_g2_scratch_write(chip, reg, chip->gpio_data[offset]); +} + +/** + * mv88e6352_g2_scratch_gpio_get_dir - get direction of gpio pin + * @chip: chip private data + * @pin: gpio index + * + * Return: 0 for output, 1 for input (same as GPIOF_DIR_XXX). + */ +static int mv88e6352_g2_scratch_gpio_get_dir(struct mv88e6xxx_chip *chip, + unsigned int pin) +{ + int val = 0; + int err; + + err = mv88e6xxx_g2_scratch_get_bit(chip, + MV88E6352_G2_SCRATCH_GPIO_DIR0, + pin, &val); + if (err) + return err; + + return val; +} + +/** + * mv88e6352_g2_scratch_gpio_set_dir - set direction of gpio pin + * @chip: chip private data + * @pin: gpio index + */ +static int mv88e6352_g2_scratch_gpio_set_dir(struct mv88e6xxx_chip *chip, + unsigned int pin, bool input) +{ + int value = (input ? MV88E6352_G2_SCRATCH_GPIO_DIR_IN : + MV88E6352_G2_SCRATCH_GPIO_DIR_OUT); + + return mv88e6xxx_g2_scratch_set_bit(chip, + MV88E6352_G2_SCRATCH_GPIO_DIR0, + pin, value); +} + +/** + * mv88e6352_g2_scratch_gpio_get_pctl - get pin control setting + * @chip: chip private data + * @pin: gpio index + * @func: function number + * + * Note that the function numbers themselves may vary by chipset. + */ +static int mv88e6352_g2_scratch_gpio_get_pctl(struct mv88e6xxx_chip *chip, + unsigned int pin, int *func) +{ + int reg = MV88E6352_G2_SCRATCH_GPIO_PCTL0 + (pin / 2); + int offset = (pin & 0x1) ? 4 : 0; + u8 mask = (0x7 << offset); + int err; + u8 val; + + err = mv88e6xxx_g2_scratch_read(chip, reg, &val); + if (err) + return err; + + *func = (val & mask) >> offset; + + return 0; +} + +/** + * mv88e6352_g2_scratch_gpio_set_pctl - set pin control setting + * @chip: chip private data + * @pin: gpio index + * @func: function number + */ +static int mv88e6352_g2_scratch_gpio_set_pctl(struct mv88e6xxx_chip *chip, + unsigned int pin, int func) +{ + int reg = MV88E6352_G2_SCRATCH_GPIO_PCTL0 + (pin / 2); + int offset = (pin & 0x1) ? 4 : 0; + u8 mask = (0x7 << offset); + int err; + u8 val; + + err = mv88e6xxx_g2_scratch_read(chip, reg, &val); + if (err) + return err; + + val = (val & ~mask) | ((func & mask) << offset); + + return mv88e6xxx_g2_scratch_write(chip, reg, val); +} + +const struct mv88e6xxx_gpio_ops mv88e6352_gpio_ops = { + .get_data = mv88e6352_g2_scratch_gpio_get_data, + .set_data = mv88e6352_g2_scratch_gpio_set_data, + .get_dir = mv88e6352_g2_scratch_gpio_get_dir, + .set_dir = mv88e6352_g2_scratch_gpio_set_dir, + .get_pctl = mv88e6352_g2_scratch_gpio_get_pctl, + .set_pctl = mv88e6352_g2_scratch_gpio_set_pctl, +}; diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c new file mode 100644 index 000000000000..b251d534b70d --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c @@ -0,0 +1,584 @@ +/* + * Marvell 88E6xxx Switch hardware timestamping 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 "hwtstamp.h" +#include "ptp.h" +#include + +#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb)) + +static int mv88e6xxx_port_ptp_read(struct mv88e6xxx_chip *chip, int port, + int addr, u16 *data, int len) +{ + if (!chip->info->ops->avb_ops->port_ptp_read) + return -EOPNOTSUPP; + + return chip->info->ops->avb_ops->port_ptp_read(chip, port, addr, + data, len); +} + +static int mv88e6xxx_port_ptp_write(struct mv88e6xxx_chip *chip, int port, + int addr, u16 data) +{ + if (!chip->info->ops->avb_ops->port_ptp_write) + return -EOPNOTSUPP; + + return chip->info->ops->avb_ops->port_ptp_write(chip, port, addr, + data); +} + +static int mv88e6xxx_ptp_write(struct mv88e6xxx_chip *chip, int addr, + u16 data) +{ + if (!chip->info->ops->avb_ops->ptp_write) + return -EOPNOTSUPP; + + return chip->info->ops->avb_ops->ptp_write(chip, addr, data); +} + +/* TX_TSTAMP_TIMEOUT: This limits the time spent polling for a TX + * timestamp. When working properly, hardware will produce a timestamp + * within 1ms. Software may enounter delays due to MDIO contention, so + * the timeout is set accordingly. + */ +#define TX_TSTAMP_TIMEOUT msecs_to_jiffies(20) + +int mv88e6xxx_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *info) +{ + struct mv88e6xxx_chip *chip = ds->priv; + + if (!chip->info->ptp_support) + return -EOPNOTSUPP; + + info->so_timestamping = + SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + info->phc_index = ptp_clock_index(chip->ptp_clock); + info->tx_types = + (1 << HWTSTAMP_TX_OFF) | + (1 << HWTSTAMP_TX_ON); + info->rx_filters = + (1 << HWTSTAMP_FILTER_NONE) | + (1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) | + (1 << HWTSTAMP_FILTER_PTP_V2_L4_SYNC) | + (1 << HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) | + (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | + (1 << HWTSTAMP_FILTER_PTP_V2_L2_SYNC) | + (1 << HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ) | + (1 << HWTSTAMP_FILTER_PTP_V2_EVENT) | + (1 << HWTSTAMP_FILTER_PTP_V2_SYNC) | + (1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ); + + return 0; +} + +static int mv88e6xxx_set_hwtstamp_config(struct mv88e6xxx_chip *chip, int port, + struct hwtstamp_config *config) +{ + struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port]; + bool tstamp_enable = false; + u16 port_config0; + int err; + + /* Prevent the TX/RX paths from trying to interact with the + * timestamp hardware while we reconfigure it. + */ + clear_bit_unlock(MV88E6XXX_HWTSTAMP_ENABLED, &ps->state); + + /* reserved for future extensions */ + if (config->flags) + return -EINVAL; + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + tstamp_enable = false; + break; + case HWTSTAMP_TX_ON: + tstamp_enable = true; + break; + default: + return -ERANGE; + } + + /* The switch supports timestamping both L2 and L4; one cannot be + * disabled independently of the other. + */ + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + tstamp_enable = false; + break; + case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; + break; + case HWTSTAMP_FILTER_ALL: + default: + config->rx_filter = HWTSTAMP_FILTER_NONE; + return -ERANGE; + } + + if (tstamp_enable) { + /* Disable transportSpecific value matching, so that packets + * with either 1588 (0) and 802.1AS (1) will be timestamped. + */ + port_config0 = MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH; + } else { + /* Disable PTP. This disables both RX and TX timestamping. */ + port_config0 = MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP; + } + + mutex_lock(&chip->reg_lock); + err = mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0, + port_config0); + mutex_unlock(&chip->reg_lock); + + if (err < 0) + return err; + + /* Once hardware has been configured, enable timestamp checks + * in the RX/TX paths. + */ + if (tstamp_enable) + set_bit(MV88E6XXX_HWTSTAMP_ENABLED, &ps->state); + + return 0; +} + +int mv88e6xxx_port_hwtstamp_set(struct dsa_switch *ds, int port, + struct ifreq *ifr) +{ + struct mv88e6xxx_chip *chip = ds->priv; + struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port]; + struct hwtstamp_config config; + int err; + + if (!chip->info->ptp_support) + return -EOPNOTSUPP; + + if (port < 0 || port >= mv88e6xxx_num_ports(chip)) + return -EINVAL; + + if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) + return -EFAULT; + + err = mv88e6xxx_set_hwtstamp_config(chip, port, &config); + if (err) + return err; + + /* Save the chosen configuration to be returned later. */ + memcpy(&ps->tstamp_config, &config, sizeof(config)); + + return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? + -EFAULT : 0; +} + +int mv88e6xxx_port_hwtstamp_get(struct dsa_switch *ds, int port, + struct ifreq *ifr) +{ + struct mv88e6xxx_chip *chip = ds->priv; + struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port]; + struct hwtstamp_config *config = &ps->tstamp_config; + + if (!chip->info->ptp_support) + return -EOPNOTSUPP; + + if (port < 0 || port >= mv88e6xxx_num_ports(chip)) + return -EINVAL; + + return copy_to_user(ifr->ifr_data, config, sizeof(*config)) ? + -EFAULT : 0; +} + +/* Get the start of the PTP header in this skb */ +static u8 *parse_ptp_header(struct sk_buff *skb, unsigned int type) +{ + u8 *data = skb_mac_header(skb); + unsigned int offset = 0; + + if (type & PTP_CLASS_VLAN) + offset += VLAN_HLEN; + + switch (type & PTP_CLASS_PMASK) { + case PTP_CLASS_IPV4: + offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN; + break; + case PTP_CLASS_IPV6: + offset += ETH_HLEN + IP6_HLEN + UDP_HLEN; + break; + case PTP_CLASS_L2: + offset += ETH_HLEN; + break; + default: + return NULL; + } + + /* Ensure that the entire header is present in this packet. */ + if (skb->len + ETH_HLEN < offset + 34) + return NULL; + + return data + offset; +} + +/* Returns a pointer to the PTP header if the caller should time stamp, + * or NULL if the caller should not. + */ +static u8 *mv88e6xxx_should_tstamp(struct mv88e6xxx_chip *chip, int port, + struct sk_buff *skb, unsigned int type) +{ + struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port]; + u8 *hdr; + + if (!chip->info->ptp_support) + return NULL; + + if (port < 0 || port >= mv88e6xxx_num_ports(chip)) + return NULL; + + hdr = parse_ptp_header(skb, type); + if (!hdr) + return NULL; + + if (!test_bit(MV88E6XXX_HWTSTAMP_ENABLED, &ps->state)) + return NULL; + + return hdr; +} + +static int mv88e6xxx_ts_valid(u16 status) +{ + if (!(status & MV88E6XXX_PTP_TS_VALID)) + return 0; + if (status & MV88E6XXX_PTP_TS_STATUS_MASK) + return 0; + return 1; +} + +static int seq_match(struct sk_buff *skb, u16 ts_seqid) +{ + unsigned int type = SKB_PTP_TYPE(skb); + u8 *hdr = parse_ptp_header(skb, type); + __be16 *seqid; + + seqid = (__be16 *)(hdr + OFF_PTP_SEQUENCE_ID); + + return ts_seqid == ntohs(*seqid); +} + +static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip, + struct mv88e6xxx_port_hwtstamp *ps, + struct sk_buff *skb, u16 reg, + struct sk_buff_head *rxq) +{ + u16 buf[4] = { 0 }, status, timelo, timehi, seq_id; + struct skb_shared_hwtstamps *shwt; + int err; + + mutex_lock(&chip->reg_lock); + err = mv88e6xxx_port_ptp_read(chip, ps->port_id, + reg, buf, ARRAY_SIZE(buf)); + mutex_unlock(&chip->reg_lock); + if (err) + pr_err("failed to get the receive time stamp\n"); + + status = buf[0]; + timelo = buf[1]; + timehi = buf[2]; + seq_id = buf[3]; + + if (status & MV88E6XXX_PTP_TS_VALID) { + mutex_lock(&chip->reg_lock); + err = mv88e6xxx_port_ptp_write(chip, ps->port_id, reg, 0); + mutex_unlock(&chip->reg_lock); + if (err) + pr_err("failed to clear the receive status\n"); + } + /* Since the device can only handle one time stamp at a time, + * we purge any extra frames from the queue. + */ + for ( ; skb; skb = skb_dequeue(rxq)) { + if (mv88e6xxx_ts_valid(status) && seq_match(skb, seq_id)) { + u64 ns = timehi << 16 | timelo; + + mutex_lock(&chip->reg_lock); + ns = timecounter_cyc2time(&chip->tstamp_tc, ns); + mutex_unlock(&chip->reg_lock); + shwt = skb_hwtstamps(skb); + memset(shwt, 0, sizeof(*shwt)); + shwt->hwtstamp = ns_to_ktime(ns); + status &= ~MV88E6XXX_PTP_TS_VALID; + } + netif_rx_ni(skb); + } +} + +static void mv88e6xxx_rxtstamp_work(struct mv88e6xxx_chip *chip, + struct mv88e6xxx_port_hwtstamp *ps) +{ + struct sk_buff *skb; + + skb = skb_dequeue(&ps->rx_queue); + + if (skb) + mv88e6xxx_get_rxts(chip, ps, skb, MV88E6XXX_PORT_PTP_ARR0_STS, + &ps->rx_queue); + + skb = skb_dequeue(&ps->rx_queue2); + if (skb) + mv88e6xxx_get_rxts(chip, ps, skb, MV88E6XXX_PORT_PTP_ARR1_STS, + &ps->rx_queue2); +} + +static int is_pdelay_resp(u8 *msgtype) +{ + return (*msgtype & 0xf) == 3; +} + +bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port, + struct sk_buff *skb, unsigned int type) +{ + struct mv88e6xxx_port_hwtstamp *ps; + struct mv88e6xxx_chip *chip; + u8 *hdr; + + chip = ds->priv; + ps = &chip->port_hwtstamp[port]; + + if (ps->tstamp_config.rx_filter != HWTSTAMP_FILTER_PTP_V2_EVENT) + return false; + + hdr = mv88e6xxx_should_tstamp(chip, port, skb, type); + if (!hdr) + return false; + + SKB_PTP_TYPE(skb) = type; + + if (is_pdelay_resp(hdr)) + skb_queue_tail(&ps->rx_queue2, skb); + else + skb_queue_tail(&ps->rx_queue, skb); + + ptp_schedule_worker(chip->ptp_clock, 0); + + return true; +} + +static int mv88e6xxx_txtstamp_work(struct mv88e6xxx_chip *chip, + struct mv88e6xxx_port_hwtstamp *ps) +{ + struct skb_shared_hwtstamps shhwtstamps; + u16 departure_block[4], status; + struct sk_buff *tmp_skb; + u32 time_raw; + int err; + u64 ns; + + if (!ps->tx_skb) + return 0; + + mutex_lock(&chip->reg_lock); + err = mv88e6xxx_port_ptp_read(chip, ps->port_id, + MV88E6XXX_PORT_PTP_DEP_STS, + departure_block, + ARRAY_SIZE(departure_block)); + mutex_unlock(&chip->reg_lock); + + if (err) + goto free_and_clear_skb; + + if (!(departure_block[0] & MV88E6XXX_PTP_TS_VALID)) { + if (time_is_before_jiffies(ps->tx_tstamp_start + + TX_TSTAMP_TIMEOUT)) { + dev_warn(chip->dev, "p%d: clearing tx timestamp hang\n", + ps->port_id); + goto free_and_clear_skb; + } + /* The timestamp should be available quickly, while getting it + * is high priority and time bounded to only 10ms. A poll is + * warranted so restart the work. + */ + return 1; + } + + /* We have the timestamp; go ahead and clear valid now */ + mutex_lock(&chip->reg_lock); + mv88e6xxx_port_ptp_write(chip, ps->port_id, + MV88E6XXX_PORT_PTP_DEP_STS, 0); + mutex_unlock(&chip->reg_lock); + + status = departure_block[0] & MV88E6XXX_PTP_TS_STATUS_MASK; + if (status != MV88E6XXX_PTP_TS_STATUS_NORMAL) { + dev_warn(chip->dev, "p%d: tx timestamp overrun\n", ps->port_id); + goto free_and_clear_skb; + } + + if (departure_block[3] != ps->tx_seq_id) { + dev_warn(chip->dev, "p%d: unexpected seq. id\n", ps->port_id); + goto free_and_clear_skb; + } + + memset(&shhwtstamps, 0, sizeof(shhwtstamps)); + time_raw = ((u32)departure_block[2] << 16) | departure_block[1]; + mutex_lock(&chip->reg_lock); + ns = timecounter_cyc2time(&chip->tstamp_tc, time_raw); + mutex_unlock(&chip->reg_lock); + shhwtstamps.hwtstamp = ns_to_ktime(ns); + + dev_dbg(chip->dev, + "p%d: txtstamp %llx status 0x%04x skb ID 0x%04x hw ID 0x%04x\n", + ps->port_id, ktime_to_ns(shhwtstamps.hwtstamp), + departure_block[0], ps->tx_seq_id, departure_block[3]); + + /* skb_complete_tx_timestamp() will free up the client to make + * another timestamp-able transmit. We have to be ready for it + * -- by clearing the ps->tx_skb "flag" -- beforehand. + */ + + tmp_skb = ps->tx_skb; + ps->tx_skb = NULL; + clear_bit_unlock(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, &ps->state); + skb_complete_tx_timestamp(tmp_skb, &shhwtstamps); + + return 0; + +free_and_clear_skb: + dev_kfree_skb_any(ps->tx_skb); + ps->tx_skb = NULL; + clear_bit_unlock(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, &ps->state); + + return 0; +} + +long mv88e6xxx_hwtstamp_work(struct ptp_clock_info *ptp) +{ + struct mv88e6xxx_chip *chip = ptp_to_chip(ptp); + struct dsa_switch *ds = chip->ds; + struct mv88e6xxx_port_hwtstamp *ps; + int i, restart = 0; + + for (i = 0; i < ds->num_ports; i++) { + if (!dsa_is_user_port(ds, i)) + continue; + + ps = &chip->port_hwtstamp[i]; + if (test_bit(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, &ps->state)) + restart |= mv88e6xxx_txtstamp_work(chip, ps); + + mv88e6xxx_rxtstamp_work(chip, ps); + } + + return restart ? 1 : -1; +} + +bool mv88e6xxx_port_txtstamp(struct dsa_switch *ds, int port, + struct sk_buff *clone, unsigned int type) +{ + struct mv88e6xxx_chip *chip = ds->priv; + struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port]; + __be16 *seq_ptr; + u8 *hdr; + + if (!(skb_shinfo(clone)->tx_flags & SKBTX_HW_TSTAMP)) + return false; + + hdr = mv88e6xxx_should_tstamp(chip, port, clone, type); + if (!hdr) + return false; + + seq_ptr = (__be16 *)(hdr + OFF_PTP_SEQUENCE_ID); + + if (test_and_set_bit_lock(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, + &ps->state)) + return false; + + ps->tx_skb = clone; + ps->tx_tstamp_start = jiffies; + ps->tx_seq_id = be16_to_cpup(seq_ptr); + + ptp_schedule_worker(chip->ptp_clock, 0); + return true; +} + +static int mv88e6xxx_hwtstamp_port_setup(struct mv88e6xxx_chip *chip, int port) +{ + struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port]; + + ps->port_id = port; + + skb_queue_head_init(&ps->rx_queue); + skb_queue_head_init(&ps->rx_queue2); + + return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0, + MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP); +} + +int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip) +{ + int err; + int i; + + /* Disable timestamping on all ports. */ + for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) { + err = mv88e6xxx_hwtstamp_port_setup(chip, i); + if (err) + return err; + } + + /* MV88E6XXX_PTP_MSG_TYPE is a mask of PTP message types to + * timestamp. This affects all ports that have timestamping enabled, + * but the timestamp config is per-port; thus we configure all events + * here and only support the HWTSTAMP_FILTER_*_EVENT filter types. + */ + err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_MSGTYPE, + MV88E6XXX_PTP_MSGTYPE_ALL_EVENT); + if (err) + return err; + + /* Use ARRIVAL1 for peer delay response messages. */ + err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR, + MV88E6XXX_PTP_MSGTYPE_PDLAY_RES); + if (err) + return err; + + /* 88E6341 devices default to timestamping at the PHY, but this has + * a hardware issue that results in unreliable timestamps. Force + * these devices to timestamp at the MAC. + */ + if (chip->info->family == MV88E6XXX_FAMILY_6341) { + u16 val = MV88E6341_PTP_CFG_UPDATE | + MV88E6341_PTP_CFG_MODE_IDX | + MV88E6341_PTP_CFG_MODE_TS_AT_MAC; + err = mv88e6xxx_ptp_write(chip, MV88E6341_PTP_CFG, val); + if (err) + return err; + } + + return 0; +} + +void mv88e6xxx_hwtstamp_free(struct mv88e6xxx_chip *chip) +{ +} diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.h b/drivers/net/dsa/mv88e6xxx/hwtstamp.h new file mode 100644 index 000000000000..bc71c9212a08 --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.h @@ -0,0 +1,172 @@ +/* + * Marvell 88E6xxx Switch hardware timestamping 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_HWTSTAMP_H +#define _MV88E6XXX_HWTSTAMP_H + +#include "chip.h" + +/* Global PTP registers */ +/* Offset 0x00: PTP EtherType */ +#define MV88E6XXX_PTP_ETHERTYPE 0x00 + +/* Offset 0x01: Message Type Timestamp Enables */ +#define MV88E6XXX_PTP_MSGTYPE 0x01 +#define MV88E6XXX_PTP_MSGTYPE_SYNC 0x0001 +#define MV88E6XXX_PTP_MSGTYPE_DELAY_REQ 0x0002 +#define MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ 0x0004 +#define MV88E6XXX_PTP_MSGTYPE_PDLAY_RES 0x0008 +#define MV88E6XXX_PTP_MSGTYPE_ALL_EVENT 0x000f + +/* Offset 0x02: Timestamp Arrival Capture Pointers */ +#define MV88E6XXX_PTP_TS_ARRIVAL_PTR 0x02 + +/* Offset 0x07: PTP Global Configuration */ +#define MV88E6341_PTP_CFG 0x07 +#define MV88E6341_PTP_CFG_UPDATE 0x8000 +#define MV88E6341_PTP_CFG_IDX_MASK 0x7f00 +#define MV88E6341_PTP_CFG_DATA_MASK 0x00ff +#define MV88E6341_PTP_CFG_MODE_IDX 0x0 +#define MV88E6341_PTP_CFG_MODE_TS_AT_PHY 0x00 +#define MV88E6341_PTP_CFG_MODE_TS_AT_MAC 0x80 + +/* Offset 0x08: PTP Interrupt Status */ +#define MV88E6XXX_PTP_IRQ_STATUS 0x08 + +/* Per-Port PTP Registers */ +/* Offset 0x00: PTP Configuration 0 */ +#define MV88E6XXX_PORT_PTP_CFG0 0x00 +#define MV88E6XXX_PORT_PTP_CFG0_TSPEC_SHIFT 12 +#define MV88E6XXX_PORT_PTP_CFG0_TSPEC_MASK 0xf000 +#define MV88E6XXX_PORT_PTP_CFG0_TSPEC_1588 0x0000 +#define MV88E6XXX_PORT_PTP_CFG0_TSPEC_8021AS 0x1000 +#define MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH 0x0800 +#define MV88E6XXX_PORT_PTP_CFG0_DISABLE_OVERWRITE 0x0002 +#define MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP 0x0001 + +/* Offset 0x01: PTP Configuration 1 */ +#define MV88E6XXX_PORT_PTP_CFG1 0x01 + +/* Offset 0x02: PTP Configuration 2 */ +#define MV88E6XXX_PORT_PTP_CFG2 0x02 +#define MV88E6XXX_PORT_PTP_CFG2_EMBED_ARRIVAL 0x1000 +#define MV88E6XXX_PORT_PTP_CFG2_DEP_IRQ_EN 0x0002 +#define MV88E6XXX_PORT_PTP_CFG2_ARR_IRQ_EN 0x0001 + +/* Offset 0x03: PTP LED Configuration */ +#define MV88E6XXX_PORT_PTP_LED_CFG 0x03 + +/* Offset 0x08: PTP Arrival 0 Status */ +#define MV88E6XXX_PORT_PTP_ARR0_STS 0x08 + +/* Offset 0x09/0x0A: PTP Arrival 0 Time */ +#define MV88E6XXX_PORT_PTP_ARR0_TIME_LO 0x09 +#define MV88E6XXX_PORT_PTP_ARR0_TIME_HI 0x0a + +/* Offset 0x0B: PTP Arrival 0 Sequence ID */ +#define MV88E6XXX_PORT_PTP_ARR0_SEQID 0x0b + +/* Offset 0x0C: PTP Arrival 1 Status */ +#define MV88E6XXX_PORT_PTP_ARR1_STS 0x0c + +/* Offset 0x0D/0x0E: PTP Arrival 1 Time */ +#define MV88E6XXX_PORT_PTP_ARR1_TIME_LO 0x0d +#define MV88E6XXX_PORT_PTP_ARR1_TIME_HI 0x0e + +/* Offset 0x0F: PTP Arrival 1 Sequence ID */ +#define MV88E6XXX_PORT_PTP_ARR1_SEQID 0x0f + +/* Offset 0x10: PTP Departure Status */ +#define MV88E6XXX_PORT_PTP_DEP_STS 0x10 + +/* Offset 0x11/0x12: PTP Deperture Time */ +#define MV88E6XXX_PORT_PTP_DEP_TIME_LO 0x11 +#define MV88E6XXX_PORT_PTP_DEP_TIME_HI 0x12 + +/* Offset 0x13: PTP Departure Sequence ID */ +#define MV88E6XXX_PORT_PTP_DEP_SEQID 0x13 + +/* Status fields for arrival and depature timestamp status registers */ +#define MV88E6XXX_PTP_TS_STATUS_MASK 0x0006 +#define MV88E6XXX_PTP_TS_STATUS_NORMAL 0x0000 +#define MV88E6XXX_PTP_TS_STATUS_OVERWITTEN 0x0002 +#define MV88E6XXX_PTP_TS_STATUS_DISCARDED 0x0004 +#define MV88E6XXX_PTP_TS_VALID 0x0001 + +#ifdef CONFIG_NET_DSA_MV88E6XXX_PTP + +int mv88e6xxx_port_hwtstamp_set(struct dsa_switch *ds, int port, + struct ifreq *ifr); +int mv88e6xxx_port_hwtstamp_get(struct dsa_switch *ds, int port, + struct ifreq *ifr); + +bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port, + struct sk_buff *clone, unsigned int type); +bool mv88e6xxx_port_txtstamp(struct dsa_switch *ds, int port, + struct sk_buff *clone, unsigned int type); + +int mv88e6xxx_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *info); + +int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip); +void mv88e6xxx_hwtstamp_free(struct mv88e6xxx_chip *chip); + +#else /* !CONFIG_NET_DSA_MV88E6XXX_PTP */ + +static inline int mv88e6xxx_port_hwtstamp_set(struct dsa_switch *ds, + int port, struct ifreq *ifr) +{ + return -EOPNOTSUPP; +} + +static inline int mv88e6xxx_port_hwtstamp_get(struct dsa_switch *ds, + int port, struct ifreq *ifr) +{ + return -EOPNOTSUPP; +} + +static inline bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port, + struct sk_buff *clone, + unsigned int type) +{ + return false; +} + +static inline bool mv88e6xxx_port_txtstamp(struct dsa_switch *ds, int port, + struct sk_buff *clone, + unsigned int type) +{ + return false; +} + +static inline int mv88e6xxx_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *info) +{ + return -EOPNOTSUPP; +} + +static inline int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip) +{ + return 0; +} + +static inline void mv88e6xxx_hwtstamp_free(struct mv88e6xxx_chip *chip) +{ +} + +#endif /* CONFIG_NET_DSA_MV88E6XXX_PTP */ + +#endif /* _MV88E6XXX_HWTSTAMP_H */ diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c new file mode 100644 index 000000000000..bd85e2c390e1 --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/ptp.c @@ -0,0 +1,381 @@ +/* + * 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 dw_overflow_to_chip(dw) container_of(dw, struct mv88e6xxx_chip, \ + overflow_work) +#define dw_tai_event_to_chip(dw) container_of(dw, struct mv88e6xxx_chip, \ + tai_event_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 int mv88e6xxx_tai_write(struct mv88e6xxx_chip *chip, int addr, u16 data) +{ + if (!chip->info->ops->avb_ops->tai_write) + return -EOPNOTSUPP; + + return chip->info->ops->avb_ops->tai_write(chip, addr, data); +} + +/* TODO: places where this are called should be using pinctrl */ +static int mv88e6xxx_set_gpio_func(struct mv88e6xxx_chip *chip, int pin, + int func, int input) +{ + int err; + + if (!chip->info->ops->gpio_ops) + return -EOPNOTSUPP; + + err = chip->info->ops->gpio_ops->set_dir(chip, pin, input); + if (err) + return err; + + return chip->info->ops->gpio_ops->set_pctl(chip, pin, func); +} + +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]; +} + +/* mv88e6xxx_config_eventcap - configure TAI event capture + * @event: PTP_CLOCK_PPS (internal) or PTP_CLOCK_EXTTS (external) + * @rising: zero for falling-edge trigger, else rising-edge trigger + * + * This will also reset the capture sequence counter. + */ +static int mv88e6xxx_config_eventcap(struct mv88e6xxx_chip *chip, int event, + int rising) +{ + u16 global_config; + u16 cap_config; + int err; + + chip->evcap_config = MV88E6XXX_TAI_CFG_CAP_OVERWRITE | + MV88E6XXX_TAI_CFG_CAP_CTR_START; + if (!rising) + chip->evcap_config |= MV88E6XXX_TAI_CFG_EVREQ_FALLING; + + global_config = (chip->evcap_config | chip->trig_config); + err = mv88e6xxx_tai_write(chip, MV88E6XXX_TAI_CFG, global_config); + if (err) + return err; + + if (event == PTP_CLOCK_PPS) { + cap_config = MV88E6XXX_TAI_EVENT_STATUS_CAP_TRIG; + } else if (event == PTP_CLOCK_EXTTS) { + /* if STATUS_CAP_TRIG is unset we capture PTP_EVREQ events */ + cap_config = 0; + } else { + return -EINVAL; + } + + /* Write the capture config; this also clears the capture counter */ + err = mv88e6xxx_tai_write(chip, MV88E6XXX_TAI_EVENT_STATUS, + cap_config); + + return err; +} + +static void mv88e6xxx_tai_event_work(struct work_struct *ugly) +{ + struct delayed_work *dw = to_delayed_work(ugly); + struct mv88e6xxx_chip *chip = dw_tai_event_to_chip(dw); + struct ptp_clock_event ev; + u16 status[4]; + u32 raw_ts; + int err; + + mutex_lock(&chip->reg_lock); + err = mv88e6xxx_tai_read(chip, MV88E6XXX_TAI_EVENT_STATUS, + status, ARRAY_SIZE(status)); + mutex_unlock(&chip->reg_lock); + + if (err) { + dev_err(chip->dev, "failed to read TAI status register\n"); + return; + } + if (status[0] & MV88E6XXX_TAI_EVENT_STATUS_ERROR) { + dev_warn(chip->dev, "missed event capture\n"); + return; + } + if (!(status[0] & MV88E6XXX_TAI_EVENT_STATUS_VALID)) + goto out; + + raw_ts = ((u32)status[2] << 16) | status[1]; + + /* Clear the valid bit so the next timestamp can come in */ + status[0] &= ~MV88E6XXX_TAI_EVENT_STATUS_VALID; + mutex_lock(&chip->reg_lock); + err = mv88e6xxx_tai_write(chip, MV88E6XXX_TAI_EVENT_STATUS, status[0]); + mutex_unlock(&chip->reg_lock); + + /* This is an external timestamp */ + ev.type = PTP_CLOCK_EXTTS; + + /* We only have one timestamping channel. */ + ev.index = 0; + mutex_lock(&chip->reg_lock); + ev.timestamp = timecounter_cyc2time(&chip->tstamp_tc, raw_ts); + mutex_unlock(&chip->reg_lock); + + ptp_clock_event(chip->ptp_clock, &ev); +out: + schedule_delayed_work(&chip->tai_event_work, TAI_EVENT_WORK_INTERVAL); +} + +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_extts(struct mv88e6xxx_chip *chip, + struct ptp_clock_request *rq, int on) +{ + int rising = (rq->extts.flags & PTP_RISING_EDGE); + int func; + int pin; + int err; + + pin = ptp_find_pin(chip->ptp_clock, PTP_PF_EXTTS, rq->extts.index); + + if (pin < 0) + return -EBUSY; + + mutex_lock(&chip->reg_lock); + + if (on) { + func = MV88E6352_G2_SCRATCH_GPIO_PCTL_EVREQ; + + err = mv88e6xxx_set_gpio_func(chip, pin, func, true); + if (err) + goto out; + + schedule_delayed_work(&chip->tai_event_work, + TAI_EVENT_WORK_INTERVAL); + + err = mv88e6xxx_config_eventcap(chip, PTP_CLOCK_EXTTS, rising); + } else { + func = MV88E6352_G2_SCRATCH_GPIO_PCTL_GPIO; + + err = mv88e6xxx_set_gpio_func(chip, pin, func, true); + + cancel_delayed_work_sync(&chip->tai_event_work); + } + +out: + mutex_unlock(&chip->reg_lock); + + return err; +} + +static int mv88e6xxx_ptp_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct mv88e6xxx_chip *chip = ptp_to_chip(ptp); + + switch (rq->type) { + case PTP_CLK_REQ_EXTTS: + return mv88e6xxx_ptp_enable_extts(chip, rq, on); + default: + return -EOPNOTSUPP; + } +} + +static int mv88e6xxx_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin, + enum ptp_pin_function func, unsigned int chan) +{ + switch (func) { + case PTP_PF_NONE: + case PTP_PF_EXTTS: + break; + case PTP_PF_PEROUT: + case PTP_PF_PHYSYNC: + return -EOPNOTSUPP; + } + return 0; +} + +/* 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) +{ + int i; + + /* 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); + INIT_DELAYED_WORK(&chip->tai_event_work, mv88e6xxx_tai_event_work); + + 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.n_ext_ts = 1; + chip->ptp_clock_info.n_per_out = 0; + chip->ptp_clock_info.n_pins = mv88e6xxx_num_gpio(chip); + chip->ptp_clock_info.pps = 0; + + for (i = 0; i < chip->ptp_clock_info.n_pins; ++i) { + struct ptp_pin_desc *ppd = &chip->pin_config[i]; + + snprintf(ppd->name, sizeof(ppd->name), "mv88e6xxx_gpio%d", i); + ppd->index = i; + ppd->func = PTP_PF_NONE; + } + chip->ptp_clock_info.pin_config = chip->pin_config; + + 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_info.do_aux_work = mv88e6xxx_hwtstamp_work; + + 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); + cancel_delayed_work_sync(&chip->tai_event_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..992818ade746 --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/ptp.h @@ -0,0 +1,108 @@ +/* + * 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 +#define MV88E6XXX_TAI_CFG_CAP_OVERWRITE 0x8000 +#define MV88E6XXX_TAI_CFG_CAP_CTR_START 0x4000 +#define MV88E6XXX_TAI_CFG_EVREQ_FALLING 0x2000 +#define MV88E6XXX_TAI_CFG_TRIG_ACTIVE_LO 0x1000 +#define MV88E6XXX_TAI_CFG_IRL_ENABLE 0x0400 +#define MV88E6XXX_TAI_CFG_TRIG_IRQ_EN 0x0200 +#define MV88E6XXX_TAI_CFG_EVREQ_IRQ_EN 0x0100 +#define MV88E6XXX_TAI_CFG_TRIG_LOCK 0x0080 +#define MV88E6XXX_TAI_CFG_BLOCK_UPDATE 0x0008 +#define MV88E6XXX_TAI_CFG_MULTI_PTP 0x0004 +#define MV88E6XXX_TAI_CFG_TRIG_MODE_ONESHOT 0x0002 +#define MV88E6XXX_TAI_CFG_TRIG_ENABLE 0x0001 + +/* 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 +#define MV88E6XXX_TAI_EVENT_STATUS_CAP_TRIG 0x4000 +#define MV88E6XXX_TAI_EVENT_STATUS_ERROR 0x0200 +#define MV88E6XXX_TAI_EVENT_STATUS_VALID 0x0100 +#define MV88E6XXX_TAI_EVENT_STATUS_CTR_MASK 0x00ff + +/* 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 + +long mv88e6xxx_hwtstamp_work(struct ptp_clock_info *ptp); +int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip); +void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip); + +#define ptp_to_chip(ptp) container_of(ptp, struct mv88e6xxx_chip, \ + ptp_clock_info) + +#else /* !CONFIG_NET_DSA_MV88E6XXX_PTP */ + +static long mv88e6xxx_hwtstamp_work(struct ptp_clock_info *ptp) +{ + return -1; +} + +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 */ diff --git a/include/linux/ptp_classify.h b/include/linux/ptp_classify.h index a079656b614c..059242030631 100644 --- a/include/linux/ptp_classify.h +++ b/include/linux/ptp_classify.h @@ -75,5 +75,9 @@ void __init ptp_classifier_init(void); static inline void ptp_classifier_init(void) { } +static inline unsigned int ptp_classify_raw(struct sk_buff *skb) +{ + return PTP_CLASS_NONE; +} #endif #endif /* _PTP_CLASSIFY_H_ */ diff --git a/include/net/dsa.h b/include/net/dsa.h index 6cb602dd970c..0ad17b63684d 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -101,6 +102,7 @@ struct dsa_platform_data { }; struct packet_type; +struct dsa_switch; struct dsa_device_ops { struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev); @@ -367,6 +369,12 @@ struct dsa_switch_ops { int (*set_wol)(struct dsa_switch *ds, int port, struct ethtool_wolinfo *w); + /* + * ethtool timestamp info + */ + int (*get_ts_info)(struct dsa_switch *ds, int port, + struct ethtool_ts_info *ts); + /* * Suspend and resume */ @@ -469,6 +477,18 @@ struct dsa_switch_ops { int port, struct net_device *br); void (*crosschip_bridge_leave)(struct dsa_switch *ds, int sw_index, int port, struct net_device *br); + + /* + * PTP functionality + */ + int (*port_hwtstamp_get)(struct dsa_switch *ds, int port, + struct ifreq *ifr); + int (*port_hwtstamp_set)(struct dsa_switch *ds, int port, + struct ifreq *ifr); + bool (*port_txtstamp)(struct dsa_switch *ds, int port, + struct sk_buff *clone, unsigned int type); + bool (*port_rxtstamp)(struct dsa_switch *ds, int port, + struct sk_buff *skb, unsigned int type); }; struct dsa_switch_driver { diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 6a9d0f50fbee..e63c554e0623 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -122,6 +123,38 @@ struct net_device *dsa_dev_to_net_device(struct device *dev) } EXPORT_SYMBOL_GPL(dsa_dev_to_net_device); +/* Determine if we should defer delivery of skb until we have a rx timestamp. + * + * Called from dsa_switch_rcv. For now, this will only work if tagging is + * enabled on the switch. Normally the MAC driver would retrieve the hardware + * timestamp when it reads the packet out of the hardware. However in a DSA + * switch, the DSA driver owning the interface to which the packet is + * delivered is never notified unless we do so here. + */ +static bool dsa_skb_defer_rx_timestamp(struct dsa_slave_priv *p, + struct sk_buff *skb) +{ + struct dsa_switch *ds = p->dp->ds; + unsigned int type; + + if (skb_headroom(skb) < ETH_HLEN) + return false; + + __skb_push(skb, ETH_HLEN); + + type = ptp_classify_raw(skb); + + __skb_pull(skb, ETH_HLEN); + + if (type == PTP_CLASS_NONE) + return false; + + if (likely(ds->ops->port_rxtstamp)) + return ds->ops->port_rxtstamp(ds, p->dp->index, skb, type); + + return false; +} + static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *unused) { @@ -157,6 +190,9 @@ static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev, s->rx_bytes += skb->len; u64_stats_update_end(&s->syncp); + if (dsa_skb_defer_rx_timestamp(p, skb)) + return 0; + netif_receive_skb(skb); return 0; diff --git a/net/dsa/slave.c b/net/dsa/slave.c index f52307296de4..3376dad6dcfd 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "dsa_priv.h" @@ -255,6 +256,22 @@ dsa_slave_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb, static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { + struct dsa_slave_priv *p = netdev_priv(dev); + struct dsa_switch *ds = p->dp->ds; + int port = p->dp->index; + + /* Pass through to switch driver if it supports timestamping */ + switch (cmd) { + case SIOCGHWTSTAMP: + if (ds->ops->port_hwtstamp_get) + return ds->ops->port_hwtstamp_get(ds, port, ifr); + break; + case SIOCSHWTSTAMP: + if (ds->ops->port_hwtstamp_set) + return ds->ops->port_hwtstamp_set(ds, port, ifr); + break; + } + if (!dev->phydev) return -ENODEV; @@ -385,6 +402,30 @@ static inline netdev_tx_t dsa_slave_netpoll_send_skb(struct net_device *dev, return NETDEV_TX_OK; } +static void dsa_skb_tx_timestamp(struct dsa_slave_priv *p, + struct sk_buff *skb) +{ + struct dsa_switch *ds = p->dp->ds; + struct sk_buff *clone; + unsigned int type; + + type = ptp_classify_raw(skb); + if (type == PTP_CLASS_NONE) + return; + + if (!ds->ops->port_txtstamp) + return; + + clone = skb_clone_sk(skb); + if (!clone) + return; + + if (ds->ops->port_txtstamp(ds, p->dp->index, clone, type)) + return; + + kfree_skb(clone); +} + static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev) { struct dsa_slave_priv *p = netdev_priv(dev); @@ -397,6 +438,11 @@ static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev) s->tx_bytes += skb->len; u64_stats_update_end(&s->syncp); + /* Identify PTP protocol packets, clone them, and pass them to the + * switch driver + */ + dsa_skb_tx_timestamp(p, skb); + /* Transmit function may have to reallocate the original SKB, * in which case it must have freed it. Only free it here on error. */ @@ -918,6 +964,18 @@ static int dsa_slave_set_rxnfc(struct net_device *dev, return ds->ops->set_rxnfc(ds, dp->index, nfc); } +static int dsa_slave_get_ts_info(struct net_device *dev, + struct ethtool_ts_info *ts) +{ + struct dsa_slave_priv *p = netdev_priv(dev); + struct dsa_switch *ds = p->dp->ds; + + if (!ds->ops->get_ts_info) + return -EOPNOTSUPP; + + return ds->ops->get_ts_info(ds, p->dp->index, ts); +} + static const struct ethtool_ops dsa_slave_ethtool_ops = { .get_drvinfo = dsa_slave_get_drvinfo, .get_regs_len = dsa_slave_get_regs_len, @@ -938,6 +996,7 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = { .set_link_ksettings = phy_ethtool_set_link_ksettings, .get_rxnfc = dsa_slave_get_rxnfc, .set_rxnfc = dsa_slave_set_rxnfc, + .get_ts_info = dsa_slave_get_ts_info, }; /* legacy way, bypassing the bridge *****************************************/