123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- // SPDX-License-Identifier: (GPL-2.0 OR MIT)
- /*
- * DSA driver for:
- * Hirschmann Hellcreek TSN switch.
- *
- * Copyright (C) 2019,2020 Hochschule Offenburg
- * Copyright (C) 2019,2020 Linutronix GmbH
- * Authors: Kamil Alkhouri <[email protected]>
- * Kurt Kanzenbach <[email protected]>
- */
- #include <linux/ptp_classify.h>
- #include "hellcreek.h"
- #include "hellcreek_hwtstamp.h"
- #include "hellcreek_ptp.h"
- int hellcreek_get_ts_info(struct dsa_switch *ds, int port,
- struct ethtool_ts_info *info)
- {
- struct hellcreek *hellcreek = ds->priv;
- info->phc_index = hellcreek->ptp_clock ?
- ptp_clock_index(hellcreek->ptp_clock) : -1;
- info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
- SOF_TIMESTAMPING_RX_HARDWARE |
- SOF_TIMESTAMPING_RAW_HARDWARE;
- /* enabled tx timestamping */
- info->tx_types = BIT(HWTSTAMP_TX_ON);
- /* L2 & L4 PTPv2 event rx messages are timestamped */
- info->rx_filters = BIT(HWTSTAMP_FILTER_PTP_V2_EVENT);
- return 0;
- }
- /* Enabling/disabling TX and RX HW timestamping for different PTP messages is
- * not available in the switch. Thus, this function only serves as a check if
- * the user requested what is actually available or not
- */
- static int hellcreek_set_hwtstamp_config(struct hellcreek *hellcreek, int port,
- struct hwtstamp_config *config)
- {
- struct hellcreek_port_hwtstamp *ps =
- &hellcreek->ports[port].port_hwtstamp;
- bool tx_tstamp_enable = false;
- bool rx_tstamp_enable = false;
- /* Interaction with the timestamp hardware is prevented here. It is
- * enabled when this config function ends successfully
- */
- clear_bit_unlock(HELLCREEK_HWTSTAMP_ENABLED, &ps->state);
- switch (config->tx_type) {
- case HWTSTAMP_TX_ON:
- tx_tstamp_enable = true;
- break;
- /* TX HW timestamping can't be disabled on the switch */
- case HWTSTAMP_TX_OFF:
- config->tx_type = HWTSTAMP_TX_ON;
- break;
- default:
- return -ERANGE;
- }
- switch (config->rx_filter) {
- /* RX HW timestamping can't be disabled on the switch */
- case HWTSTAMP_FILTER_NONE:
- config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
- 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;
- rx_tstamp_enable = true;
- break;
- /* RX HW timestamping can't be enabled for all messages on the switch */
- case HWTSTAMP_FILTER_ALL:
- config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
- break;
- default:
- return -ERANGE;
- }
- if (!tx_tstamp_enable)
- return -ERANGE;
- if (!rx_tstamp_enable)
- return -ERANGE;
- /* If this point is reached, then the requested hwtstamp config is
- * compatible with the hwtstamp offered by the switch. Therefore,
- * enable the interaction with the HW timestamping
- */
- set_bit(HELLCREEK_HWTSTAMP_ENABLED, &ps->state);
- return 0;
- }
- int hellcreek_port_hwtstamp_set(struct dsa_switch *ds, int port,
- struct ifreq *ifr)
- {
- struct hellcreek *hellcreek = ds->priv;
- struct hellcreek_port_hwtstamp *ps;
- struct hwtstamp_config config;
- int err;
- ps = &hellcreek->ports[port].port_hwtstamp;
- if (copy_from_user(&config, ifr->ifr_data, sizeof(config)))
- return -EFAULT;
- err = hellcreek_set_hwtstamp_config(hellcreek, 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 hellcreek_port_hwtstamp_get(struct dsa_switch *ds, int port,
- struct ifreq *ifr)
- {
- struct hellcreek *hellcreek = ds->priv;
- struct hellcreek_port_hwtstamp *ps;
- struct hwtstamp_config *config;
- ps = &hellcreek->ports[port].port_hwtstamp;
- config = &ps->tstamp_config;
- return copy_to_user(ifr->ifr_data, config, sizeof(*config)) ?
- -EFAULT : 0;
- }
- /* Returns a pointer to the PTP header if the caller should time stamp, or NULL
- * if the caller should not.
- */
- static struct ptp_header *hellcreek_should_tstamp(struct hellcreek *hellcreek,
- int port, struct sk_buff *skb,
- unsigned int type)
- {
- struct hellcreek_port_hwtstamp *ps =
- &hellcreek->ports[port].port_hwtstamp;
- struct ptp_header *hdr;
- hdr = ptp_parse_header(skb, type);
- if (!hdr)
- return NULL;
- if (!test_bit(HELLCREEK_HWTSTAMP_ENABLED, &ps->state))
- return NULL;
- return hdr;
- }
- static u64 hellcreek_get_reserved_field(const struct ptp_header *hdr)
- {
- return be32_to_cpu(hdr->reserved2);
- }
- static void hellcreek_clear_reserved_field(struct ptp_header *hdr)
- {
- hdr->reserved2 = 0;
- }
- static int hellcreek_ptp_hwtstamp_available(struct hellcreek *hellcreek,
- unsigned int ts_reg)
- {
- u16 status;
- status = hellcreek_ptp_read(hellcreek, ts_reg);
- if (status & PR_TS_STATUS_TS_LOST)
- dev_err(hellcreek->dev,
- "Tx time stamp lost! This should never happen!\n");
- /* If hwtstamp is not available, this means the previous hwtstamp was
- * successfully read, and the one we need is not yet available
- */
- return (status & PR_TS_STATUS_TS_AVAIL) ? 1 : 0;
- }
- /* Get nanoseconds timestamp from timestamping unit */
- static u64 hellcreek_ptp_hwtstamp_read(struct hellcreek *hellcreek,
- unsigned int ts_reg)
- {
- u16 nsl, nsh;
- nsh = hellcreek_ptp_read(hellcreek, ts_reg);
- nsh = hellcreek_ptp_read(hellcreek, ts_reg);
- nsh = hellcreek_ptp_read(hellcreek, ts_reg);
- nsh = hellcreek_ptp_read(hellcreek, ts_reg);
- nsl = hellcreek_ptp_read(hellcreek, ts_reg);
- return (u64)nsl | ((u64)nsh << 16);
- }
- static int hellcreek_txtstamp_work(struct hellcreek *hellcreek,
- struct hellcreek_port_hwtstamp *ps, int port)
- {
- struct skb_shared_hwtstamps shhwtstamps;
- unsigned int status_reg, data_reg;
- struct sk_buff *tmp_skb;
- int ts_status;
- u64 ns = 0;
- if (!ps->tx_skb)
- return 0;
- switch (port) {
- case 2:
- status_reg = PR_TS_TX_P1_STATUS_C;
- data_reg = PR_TS_TX_P1_DATA_C;
- break;
- case 3:
- status_reg = PR_TS_TX_P2_STATUS_C;
- data_reg = PR_TS_TX_P2_DATA_C;
- break;
- default:
- dev_err(hellcreek->dev, "Wrong port for timestamping!\n");
- return 0;
- }
- ts_status = hellcreek_ptp_hwtstamp_available(hellcreek, status_reg);
- /* Not available yet? */
- if (ts_status == 0) {
- /* Check whether the operation of reading the tx timestamp has
- * exceeded its allowed period
- */
- if (time_is_before_jiffies(ps->tx_tstamp_start +
- TX_TSTAMP_TIMEOUT)) {
- dev_err(hellcreek->dev,
- "Timeout while waiting for Tx timestamp!\n");
- goto free_and_clear_skb;
- }
- /* The timestamp should be available quickly, while getting it
- * in high priority. Restart the work
- */
- return 1;
- }
- mutex_lock(&hellcreek->ptp_lock);
- ns = hellcreek_ptp_hwtstamp_read(hellcreek, data_reg);
- ns += hellcreek_ptp_gettime_seconds(hellcreek, ns);
- mutex_unlock(&hellcreek->ptp_lock);
- /* Now we have the timestamp in nanoseconds, store it in the correct
- * structure in order to send it to the user
- */
- memset(&shhwtstamps, 0, sizeof(shhwtstamps));
- shhwtstamps.hwtstamp = ns_to_ktime(ns);
- tmp_skb = ps->tx_skb;
- ps->tx_skb = NULL;
- /* skb_complete_tx_timestamp() frees up the client to make another
- * timestampable transmit. We have to be ready for it by clearing the
- * ps->tx_skb "flag" beforehand
- */
- clear_bit_unlock(HELLCREEK_HWTSTAMP_TX_IN_PROGRESS, &ps->state);
- /* Deliver a clone of the original outgoing tx_skb with tx hwtstamp */
- 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(HELLCREEK_HWTSTAMP_TX_IN_PROGRESS, &ps->state);
- return 0;
- }
- static void hellcreek_get_rxts(struct hellcreek *hellcreek,
- struct hellcreek_port_hwtstamp *ps,
- struct sk_buff *skb, struct sk_buff_head *rxq,
- int port)
- {
- struct skb_shared_hwtstamps *shwt;
- struct sk_buff_head received;
- unsigned long flags;
- /* The latched timestamp belongs to one of the received frames. */
- __skb_queue_head_init(&received);
- /* Lock & disable interrupts */
- spin_lock_irqsave(&rxq->lock, flags);
- /* Add the reception queue "rxq" to the "received" queue an reintialize
- * "rxq". From now on, we deal with "received" not with "rxq"
- */
- skb_queue_splice_tail_init(rxq, &received);
- spin_unlock_irqrestore(&rxq->lock, flags);
- for (; skb; skb = __skb_dequeue(&received)) {
- struct ptp_header *hdr;
- unsigned int type;
- u64 ns;
- /* Get nanoseconds from ptp packet */
- type = SKB_PTP_TYPE(skb);
- hdr = ptp_parse_header(skb, type);
- ns = hellcreek_get_reserved_field(hdr);
- hellcreek_clear_reserved_field(hdr);
- /* Add seconds part */
- mutex_lock(&hellcreek->ptp_lock);
- ns += hellcreek_ptp_gettime_seconds(hellcreek, ns);
- mutex_unlock(&hellcreek->ptp_lock);
- /* Save time stamp */
- shwt = skb_hwtstamps(skb);
- memset(shwt, 0, sizeof(*shwt));
- shwt->hwtstamp = ns_to_ktime(ns);
- netif_rx(skb);
- }
- }
- static void hellcreek_rxtstamp_work(struct hellcreek *hellcreek,
- struct hellcreek_port_hwtstamp *ps,
- int port)
- {
- struct sk_buff *skb;
- skb = skb_dequeue(&ps->rx_queue);
- if (skb)
- hellcreek_get_rxts(hellcreek, ps, skb, &ps->rx_queue, port);
- }
- long hellcreek_hwtstamp_work(struct ptp_clock_info *ptp)
- {
- struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
- struct dsa_switch *ds = hellcreek->ds;
- int i, restart = 0;
- for (i = 0; i < ds->num_ports; i++) {
- struct hellcreek_port_hwtstamp *ps;
- if (!dsa_is_user_port(ds, i))
- continue;
- ps = &hellcreek->ports[i].port_hwtstamp;
- if (test_bit(HELLCREEK_HWTSTAMP_TX_IN_PROGRESS, &ps->state))
- restart |= hellcreek_txtstamp_work(hellcreek, ps, i);
- hellcreek_rxtstamp_work(hellcreek, ps, i);
- }
- return restart ? 1 : -1;
- }
- void hellcreek_port_txtstamp(struct dsa_switch *ds, int port,
- struct sk_buff *skb)
- {
- struct hellcreek *hellcreek = ds->priv;
- struct hellcreek_port_hwtstamp *ps;
- struct ptp_header *hdr;
- struct sk_buff *clone;
- unsigned int type;
- ps = &hellcreek->ports[port].port_hwtstamp;
- type = ptp_classify_raw(skb);
- if (type == PTP_CLASS_NONE)
- return;
- /* Make sure the message is a PTP message that needs to be timestamped
- * and the interaction with the HW timestamping is enabled. If not, stop
- * here
- */
- hdr = hellcreek_should_tstamp(hellcreek, port, skb, type);
- if (!hdr)
- return;
- clone = skb_clone_sk(skb);
- if (!clone)
- return;
- if (test_and_set_bit_lock(HELLCREEK_HWTSTAMP_TX_IN_PROGRESS,
- &ps->state)) {
- kfree_skb(clone);
- return;
- }
- ps->tx_skb = clone;
- /* store the number of ticks occurred since system start-up till this
- * moment
- */
- ps->tx_tstamp_start = jiffies;
- ptp_schedule_worker(hellcreek->ptp_clock, 0);
- }
- bool hellcreek_port_rxtstamp(struct dsa_switch *ds, int port,
- struct sk_buff *skb, unsigned int type)
- {
- struct hellcreek *hellcreek = ds->priv;
- struct hellcreek_port_hwtstamp *ps;
- struct ptp_header *hdr;
- ps = &hellcreek->ports[port].port_hwtstamp;
- /* This check only fails if the user did not initialize hardware
- * timestamping beforehand.
- */
- if (ps->tstamp_config.rx_filter != HWTSTAMP_FILTER_PTP_V2_EVENT)
- return false;
- /* Make sure the message is a PTP message that needs to be timestamped
- * and the interaction with the HW timestamping is enabled. If not, stop
- * here
- */
- hdr = hellcreek_should_tstamp(hellcreek, port, skb, type);
- if (!hdr)
- return false;
- SKB_PTP_TYPE(skb) = type;
- skb_queue_tail(&ps->rx_queue, skb);
- ptp_schedule_worker(hellcreek->ptp_clock, 0);
- return true;
- }
- static void hellcreek_hwtstamp_port_setup(struct hellcreek *hellcreek, int port)
- {
- struct hellcreek_port_hwtstamp *ps =
- &hellcreek->ports[port].port_hwtstamp;
- skb_queue_head_init(&ps->rx_queue);
- }
- int hellcreek_hwtstamp_setup(struct hellcreek *hellcreek)
- {
- struct dsa_switch *ds = hellcreek->ds;
- int i;
- /* Initialize timestamping ports. */
- for (i = 0; i < ds->num_ports; ++i) {
- if (!dsa_is_user_port(ds, i))
- continue;
- hellcreek_hwtstamp_port_setup(hellcreek, i);
- }
- /* Select the synchronized clock as the source timekeeper for the
- * timestamps and enable inline timestamping.
- */
- hellcreek_ptp_write(hellcreek, PR_SETTINGS_C_TS_SRC_TK_MASK |
- PR_SETTINGS_C_RES3TS,
- PR_SETTINGS_C);
- return 0;
- }
- void hellcreek_hwtstamp_free(struct hellcreek *hellcreek)
- {
- /* Nothing todo */
- }
|