| // SPDX-License-Identifier: GPL-2.0-only |
| /* Atlantic Network Driver |
| * |
| * Copyright (C) 2014-2019 aQuantia Corporation |
| * Copyright (C) 2019-2020 Marvell International Ltd. |
| */ |
| |
| /* File aq_ptp.c: |
| * Definition of functions for Linux PTP support. |
| */ |
| |
| #include <linux/ptp_clock_kernel.h> |
| #include <linux/ptp_classify.h> |
| #include <linux/interrupt.h> |
| #include <linux/clocksource.h> |
| |
| #include "aq_nic.h" |
| #include "aq_ptp.h" |
| #include "aq_ring.h" |
| #include "aq_phy.h" |
| #include "aq_filters.h" |
| |
| #if IS_REACHABLE(CONFIG_PTP_1588_CLOCK) |
| |
| #define AQ_PTP_TX_TIMEOUT (HZ * 10) |
| |
| #define POLL_SYNC_TIMER_MS 15 |
| |
| enum ptp_speed_offsets { |
| ptp_offset_idx_10 = 0, |
| ptp_offset_idx_100, |
| ptp_offset_idx_1000, |
| ptp_offset_idx_2500, |
| ptp_offset_idx_5000, |
| ptp_offset_idx_10000, |
| }; |
| |
| struct ptp_skb_ring { |
| struct sk_buff **buff; |
| spinlock_t lock; |
| unsigned int size; |
| unsigned int head; |
| unsigned int tail; |
| }; |
| |
| struct ptp_tx_timeout { |
| spinlock_t lock; |
| bool active; |
| unsigned long tx_start; |
| }; |
| |
| struct aq_ptp_s { |
| struct aq_nic_s *aq_nic; |
| struct hwtstamp_config hwtstamp_config; |
| spinlock_t ptp_lock; |
| spinlock_t ptp_ring_lock; |
| struct ptp_clock *ptp_clock; |
| struct ptp_clock_info ptp_info; |
| |
| atomic_t offset_egress; |
| atomic_t offset_ingress; |
| |
| struct aq_ring_param_s ptp_ring_param; |
| |
| struct ptp_tx_timeout ptp_tx_timeout; |
| |
| unsigned int idx_vector; |
| struct napi_struct napi; |
| |
| struct aq_ring_s ptp_tx; |
| struct aq_ring_s ptp_rx; |
| struct aq_ring_s hwts_rx; |
| |
| struct ptp_skb_ring skb_ring; |
| |
| struct aq_rx_filter_l3l4 udp_filter; |
| struct aq_rx_filter_l2 eth_type_filter; |
| |
| struct delayed_work poll_sync; |
| u32 poll_timeout_ms; |
| |
| bool extts_pin_enabled; |
| u64 last_sync1588_ts; |
| |
| bool a1_ptp; |
| }; |
| |
| struct ptp_tm_offset { |
| unsigned int mbps; |
| int egress; |
| int ingress; |
| }; |
| |
| static struct ptp_tm_offset ptp_offset[6]; |
| |
| void aq_ptp_tm_offset_set(struct aq_nic_s *aq_nic, unsigned int mbps) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| int i, egress, ingress; |
| |
| if (!aq_ptp) |
| return; |
| |
| egress = 0; |
| ingress = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(ptp_offset); i++) { |
| if (mbps == ptp_offset[i].mbps) { |
| egress = ptp_offset[i].egress; |
| ingress = ptp_offset[i].ingress; |
| break; |
| } |
| } |
| |
| atomic_set(&aq_ptp->offset_egress, egress); |
| atomic_set(&aq_ptp->offset_ingress, ingress); |
| } |
| |
| static int __aq_ptp_skb_put(struct ptp_skb_ring *ring, struct sk_buff *skb) |
| { |
| unsigned int next_head = (ring->head + 1) % ring->size; |
| |
| if (next_head == ring->tail) |
| return -ENOMEM; |
| |
| ring->buff[ring->head] = skb_get(skb); |
| ring->head = next_head; |
| |
| return 0; |
| } |
| |
| static int aq_ptp_skb_put(struct ptp_skb_ring *ring, struct sk_buff *skb) |
| { |
| unsigned long flags; |
| int ret; |
| |
| spin_lock_irqsave(&ring->lock, flags); |
| ret = __aq_ptp_skb_put(ring, skb); |
| spin_unlock_irqrestore(&ring->lock, flags); |
| |
| return ret; |
| } |
| |
| static struct sk_buff *__aq_ptp_skb_get(struct ptp_skb_ring *ring) |
| { |
| struct sk_buff *skb; |
| |
| if (ring->tail == ring->head) |
| return NULL; |
| |
| skb = ring->buff[ring->tail]; |
| ring->tail = (ring->tail + 1) % ring->size; |
| |
| return skb; |
| } |
| |
| static struct sk_buff *aq_ptp_skb_get(struct ptp_skb_ring *ring) |
| { |
| unsigned long flags; |
| struct sk_buff *skb; |
| |
| spin_lock_irqsave(&ring->lock, flags); |
| skb = __aq_ptp_skb_get(ring); |
| spin_unlock_irqrestore(&ring->lock, flags); |
| |
| return skb; |
| } |
| |
| static unsigned int aq_ptp_skb_buf_len(struct ptp_skb_ring *ring) |
| { |
| unsigned long flags; |
| unsigned int len; |
| |
| spin_lock_irqsave(&ring->lock, flags); |
| len = (ring->head >= ring->tail) ? |
| ring->head - ring->tail : |
| ring->size - ring->tail + ring->head; |
| spin_unlock_irqrestore(&ring->lock, flags); |
| |
| return len; |
| } |
| |
| static int aq_ptp_skb_ring_init(struct ptp_skb_ring *ring, unsigned int size) |
| { |
| struct sk_buff **buff = kmalloc(sizeof(*buff) * size, GFP_KERNEL); |
| |
| if (!buff) |
| return -ENOMEM; |
| |
| spin_lock_init(&ring->lock); |
| |
| ring->buff = buff; |
| ring->size = size; |
| ring->head = 0; |
| ring->tail = 0; |
| |
| return 0; |
| } |
| |
| static void aq_ptp_skb_ring_clean(struct ptp_skb_ring *ring) |
| { |
| struct sk_buff *skb; |
| |
| while ((skb = aq_ptp_skb_get(ring)) != NULL) |
| dev_kfree_skb_any(skb); |
| } |
| |
| static void aq_ptp_skb_ring_release(struct ptp_skb_ring *ring) |
| { |
| if (ring->buff) { |
| aq_ptp_skb_ring_clean(ring); |
| kfree(ring->buff); |
| ring->buff = NULL; |
| } |
| } |
| |
| static void aq_ptp_tx_timeout_init(struct ptp_tx_timeout *timeout) |
| { |
| spin_lock_init(&timeout->lock); |
| timeout->active = false; |
| } |
| |
| static void aq_ptp_tx_timeout_start(struct aq_ptp_s *aq_ptp) |
| { |
| struct ptp_tx_timeout *timeout = &aq_ptp->ptp_tx_timeout; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&timeout->lock, flags); |
| timeout->active = true; |
| timeout->tx_start = jiffies; |
| spin_unlock_irqrestore(&timeout->lock, flags); |
| } |
| |
| static void aq_ptp_tx_timeout_update(struct aq_ptp_s *aq_ptp) |
| { |
| if (!aq_ptp_skb_buf_len(&aq_ptp->skb_ring)) { |
| struct ptp_tx_timeout *timeout = &aq_ptp->ptp_tx_timeout; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&timeout->lock, flags); |
| timeout->active = false; |
| spin_unlock_irqrestore(&timeout->lock, flags); |
| } |
| } |
| |
| static void aq_ptp_tx_timeout_check(struct aq_ptp_s *aq_ptp) |
| { |
| struct ptp_tx_timeout *timeout = &aq_ptp->ptp_tx_timeout; |
| unsigned long flags; |
| bool timeout_flag; |
| |
| timeout_flag = false; |
| |
| spin_lock_irqsave(&timeout->lock, flags); |
| if (timeout->active) { |
| timeout_flag = time_is_before_jiffies(timeout->tx_start + |
| AQ_PTP_TX_TIMEOUT); |
| /* reset active flag if timeout detected */ |
| if (timeout_flag) |
| timeout->active = false; |
| } |
| spin_unlock_irqrestore(&timeout->lock, flags); |
| |
| if (timeout_flag) { |
| aq_ptp_skb_ring_clean(&aq_ptp->skb_ring); |
| netdev_err(aq_ptp->aq_nic->ndev, |
| "PTP Timeout. Clearing Tx Timestamp SKBs\n"); |
| } |
| } |
| |
| /* aq_ptp_adjfine |
| * @ptp: the ptp clock structure |
| * @ppb: parts per billion adjustment from base |
| * |
| * adjust the frequency of the ptp cycle counter by the |
| * indicated ppb from the base frequency. |
| */ |
| static int aq_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) |
| { |
| struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info); |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| |
| mutex_lock(&aq_nic->fwreq_mutex); |
| aq_nic->aq_hw_ops->hw_adj_clock_freq(aq_nic->aq_hw, |
| scaled_ppm_to_ppb(scaled_ppm)); |
| mutex_unlock(&aq_nic->fwreq_mutex); |
| |
| return 0; |
| } |
| |
| /* aq_ptp_adjtime |
| * @ptp: the ptp clock structure |
| * @delta: offset to adjust the cycle counter by |
| * |
| * adjust the timer by resetting the timecounter structure. |
| */ |
| static int aq_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) |
| { |
| struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info); |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&aq_ptp->ptp_lock, flags); |
| aq_nic->aq_hw_ops->hw_adj_sys_clock(aq_nic->aq_hw, delta); |
| spin_unlock_irqrestore(&aq_ptp->ptp_lock, flags); |
| |
| return 0; |
| } |
| |
| /* aq_ptp_gettime |
| * @ptp: the ptp clock structure |
| * @ts: timespec structure to hold the current time value |
| * |
| * read the timecounter and return the correct value on ns, |
| * after converting it into a struct timespec. |
| */ |
| static int aq_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) |
| { |
| struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info); |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| unsigned long flags; |
| u64 ns; |
| |
| spin_lock_irqsave(&aq_ptp->ptp_lock, flags); |
| aq_nic->aq_hw_ops->hw_get_ptp_ts(aq_nic->aq_hw, &ns); |
| spin_unlock_irqrestore(&aq_ptp->ptp_lock, flags); |
| |
| *ts = ns_to_timespec64(ns); |
| |
| return 0; |
| } |
| |
| /* aq_ptp_settime |
| * @ptp: the ptp clock structure |
| * @ts: the timespec containing the new time for the cycle counter |
| * |
| * reset the timecounter to use a new base value instead of the kernel |
| * wall timer value. |
| */ |
| static int aq_ptp_settime(struct ptp_clock_info *ptp, |
| const struct timespec64 *ts) |
| { |
| struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info); |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| unsigned long flags; |
| u64 ns = timespec64_to_ns(ts); |
| u64 now; |
| |
| spin_lock_irqsave(&aq_ptp->ptp_lock, flags); |
| aq_nic->aq_hw_ops->hw_get_ptp_ts(aq_nic->aq_hw, &now); |
| aq_nic->aq_hw_ops->hw_adj_sys_clock(aq_nic->aq_hw, (s64)ns - (s64)now); |
| |
| spin_unlock_irqrestore(&aq_ptp->ptp_lock, flags); |
| |
| return 0; |
| } |
| |
| static void aq_ptp_convert_to_hwtstamp(struct aq_ptp_s *aq_ptp, |
| struct skb_shared_hwtstamps *hwtstamp, |
| u64 timestamp) |
| { |
| memset(hwtstamp, 0, sizeof(*hwtstamp)); |
| hwtstamp->hwtstamp = ns_to_ktime(timestamp); |
| } |
| |
| static int aq_ptp_hw_pin_conf(struct aq_nic_s *aq_nic, u32 pin_index, u64 start, |
| u64 period) |
| { |
| if (period) |
| netdev_dbg(aq_nic->ndev, |
| "Enable GPIO %d pulsing, start time %llu, period %u\n", |
| pin_index, start, (u32)period); |
| else |
| netdev_dbg(aq_nic->ndev, |
| "Disable GPIO %d pulsing, start time %llu, period %u\n", |
| pin_index, start, (u32)period); |
| |
| /* Notify hardware of request to being sending pulses. |
| * If period is ZERO then pulsen is disabled. |
| */ |
| mutex_lock(&aq_nic->fwreq_mutex); |
| aq_nic->aq_hw_ops->hw_gpio_pulse(aq_nic->aq_hw, pin_index, |
| start, (u32)period); |
| mutex_unlock(&aq_nic->fwreq_mutex); |
| |
| return 0; |
| } |
| |
| static int aq_ptp_perout_pin_configure(struct ptp_clock_info *ptp, |
| struct ptp_clock_request *rq, int on) |
| { |
| struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info); |
| struct ptp_clock_time *t = &rq->perout.period; |
| struct ptp_clock_time *s = &rq->perout.start; |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| u64 start, period; |
| u32 pin_index = rq->perout.index; |
| |
| /* verify the request channel is there */ |
| if (pin_index >= ptp->n_per_out) |
| return -EINVAL; |
| |
| /* we cannot support periods greater |
| * than 4 seconds due to reg limit |
| */ |
| if (t->sec > 4 || t->sec < 0) |
| return -ERANGE; |
| |
| /* convert to unsigned 64b ns, |
| * verify we can put it in a 32b register |
| */ |
| period = on ? t->sec * NSEC_PER_SEC + t->nsec : 0; |
| |
| /* verify the value is in range supported by hardware */ |
| if (period > U32_MAX) |
| return -ERANGE; |
| /* convert to unsigned 64b ns */ |
| /* TODO convert to AQ time */ |
| start = on ? s->sec * NSEC_PER_SEC + s->nsec : 0; |
| |
| aq_ptp_hw_pin_conf(aq_nic, pin_index, start, period); |
| |
| return 0; |
| } |
| |
| static int aq_ptp_pps_pin_configure(struct ptp_clock_info *ptp, |
| struct ptp_clock_request *rq, int on) |
| { |
| struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info); |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| u64 start, period; |
| u32 pin_index = 0; |
| u32 rest = 0; |
| |
| /* verify the request channel is there */ |
| if (pin_index >= ptp->n_per_out) |
| return -EINVAL; |
| |
| aq_nic->aq_hw_ops->hw_get_ptp_ts(aq_nic->aq_hw, &start); |
| div_u64_rem(start, NSEC_PER_SEC, &rest); |
| period = on ? NSEC_PER_SEC : 0; /* PPS - pulse per second */ |
| start = on ? start - rest + NSEC_PER_SEC * |
| (rest > 990000000LL ? 2 : 1) : 0; |
| |
| aq_ptp_hw_pin_conf(aq_nic, pin_index, start, period); |
| |
| return 0; |
| } |
| |
| static void aq_ptp_extts_pin_ctrl(struct aq_ptp_s *aq_ptp) |
| { |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| u32 enable = aq_ptp->extts_pin_enabled; |
| |
| if (aq_nic->aq_hw_ops->hw_extts_gpio_enable) |
| aq_nic->aq_hw_ops->hw_extts_gpio_enable(aq_nic->aq_hw, 0, |
| enable); |
| } |
| |
| static int aq_ptp_extts_pin_configure(struct ptp_clock_info *ptp, |
| struct ptp_clock_request *rq, int on) |
| { |
| struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info); |
| |
| u32 pin_index = rq->extts.index; |
| |
| if (pin_index >= ptp->n_ext_ts) |
| return -EINVAL; |
| |
| aq_ptp->extts_pin_enabled = !!on; |
| if (on) { |
| aq_ptp->poll_timeout_ms = POLL_SYNC_TIMER_MS; |
| cancel_delayed_work_sync(&aq_ptp->poll_sync); |
| schedule_delayed_work(&aq_ptp->poll_sync, |
| msecs_to_jiffies(aq_ptp->poll_timeout_ms)); |
| } |
| |
| aq_ptp_extts_pin_ctrl(aq_ptp); |
| return 0; |
| } |
| |
| /* aq_ptp_gpio_feature_enable |
| * @ptp: the ptp clock structure |
| * @rq: the requested feature to change |
| * @on: whether to enable or disable the feature |
| */ |
| static int aq_ptp_gpio_feature_enable(struct ptp_clock_info *ptp, |
| struct ptp_clock_request *rq, int on) |
| { |
| switch (rq->type) { |
| case PTP_CLK_REQ_EXTTS: |
| return aq_ptp_extts_pin_configure(ptp, rq, on); |
| case PTP_CLK_REQ_PEROUT: |
| return aq_ptp_perout_pin_configure(ptp, rq, on); |
| case PTP_CLK_REQ_PPS: |
| return aq_ptp_pps_pin_configure(ptp, rq, on); |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| /* aq_ptp_verify |
| * @ptp: the ptp clock structure |
| * @pin: index of the pin in question |
| * @func: the desired function to use |
| * @chan: the function channel index to use |
| */ |
| static int aq_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin, |
| enum ptp_pin_function func, unsigned int chan) |
| { |
| /* verify the requested pin is there */ |
| if (!ptp->pin_config || pin >= ptp->n_pins) |
| return -EINVAL; |
| |
| /* enforce locked channels, no changing them */ |
| if (chan != ptp->pin_config[pin].chan) |
| return -EINVAL; |
| |
| /* we want to keep the functions locked as well */ |
| if (func != ptp->pin_config[pin].func) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| /* aq_ptp_tx_hwtstamp - utility function which checks for TX time stamp |
| * @adapter: the private adapter struct |
| * |
| * if the timestamp is valid, we convert it into the timecounter ns |
| * value, then store that result into the hwtstamps structure which |
| * is passed up the network stack |
| */ |
| void aq_ptp_tx_hwtstamp(struct aq_nic_s *aq_nic, u64 timestamp) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| struct sk_buff *skb = aq_ptp_skb_get(&aq_ptp->skb_ring); |
| struct skb_shared_hwtstamps hwtstamp; |
| |
| if (!skb) { |
| netdev_err(aq_nic->ndev, "have timestamp but tx_queues empty\n"); |
| return; |
| } |
| |
| timestamp += atomic_read(&aq_ptp->offset_egress); |
| aq_ptp_convert_to_hwtstamp(aq_ptp, &hwtstamp, timestamp); |
| skb_tstamp_tx(skb, &hwtstamp); |
| dev_kfree_skb_any(skb); |
| |
| aq_ptp_tx_timeout_update(aq_ptp); |
| } |
| |
| /* aq_ptp_rx_hwtstamp - utility function which checks for RX time stamp |
| * @adapter: pointer to adapter struct |
| * @skb: particular skb to send timestamp with |
| * |
| * if the timestamp is valid, we convert it into the timecounter ns |
| * value, then store that result into the hwtstamps structure which |
| * is passed up the network stack |
| */ |
| static void aq_ptp_rx_hwtstamp(struct aq_ptp_s *aq_ptp, struct sk_buff *skb, |
| u64 timestamp) |
| { |
| timestamp -= atomic_read(&aq_ptp->offset_ingress); |
| aq_ptp_convert_to_hwtstamp(aq_ptp, skb_hwtstamps(skb), timestamp); |
| } |
| |
| void aq_ptp_hwtstamp_config_get(struct aq_ptp_s *aq_ptp, |
| struct hwtstamp_config *config) |
| { |
| *config = aq_ptp->hwtstamp_config; |
| } |
| |
| static void aq_ptp_prepare_filters(struct aq_ptp_s *aq_ptp) |
| { |
| aq_ptp->udp_filter.cmd = HW_ATL_RX_ENABLE_FLTR_L3L4 | |
| HW_ATL_RX_ENABLE_CMP_PROT_L4 | |
| HW_ATL_RX_UDP | |
| HW_ATL_RX_ENABLE_CMP_DEST_PORT_L4 | |
| HW_ATL_RX_HOST << HW_ATL_RX_ACTION_FL3F4_SHIFT | |
| HW_ATL_RX_ENABLE_QUEUE_L3L4 | |
| aq_ptp->ptp_rx.idx << HW_ATL_RX_QUEUE_FL3L4_SHIFT; |
| aq_ptp->udp_filter.p_dst = PTP_EV_PORT; |
| |
| aq_ptp->eth_type_filter.ethertype = ETH_P_1588; |
| aq_ptp->eth_type_filter.queue = aq_ptp->ptp_rx.idx; |
| } |
| |
| int aq_ptp_hwtstamp_config_set(struct aq_ptp_s *aq_ptp, |
| struct hwtstamp_config *config) |
| { |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| const struct aq_hw_ops *hw_ops; |
| int err = 0; |
| |
| hw_ops = aq_nic->aq_hw_ops; |
| if (config->tx_type == HWTSTAMP_TX_ON || |
| config->rx_filter == HWTSTAMP_FILTER_PTP_V2_EVENT) { |
| aq_ptp_prepare_filters(aq_ptp); |
| if (hw_ops->hw_filter_l3l4_set) { |
| err = hw_ops->hw_filter_l3l4_set(aq_nic->aq_hw, |
| &aq_ptp->udp_filter); |
| } |
| if (!err && hw_ops->hw_filter_l2_set) { |
| err = hw_ops->hw_filter_l2_set(aq_nic->aq_hw, |
| &aq_ptp->eth_type_filter); |
| } |
| aq_utils_obj_set(&aq_nic->flags, AQ_NIC_PTP_DPATH_UP); |
| } else { |
| aq_ptp->udp_filter.cmd &= ~HW_ATL_RX_ENABLE_FLTR_L3L4; |
| if (hw_ops->hw_filter_l3l4_set) { |
| err = hw_ops->hw_filter_l3l4_set(aq_nic->aq_hw, |
| &aq_ptp->udp_filter); |
| } |
| if (!err && hw_ops->hw_filter_l2_clear) { |
| err = hw_ops->hw_filter_l2_clear(aq_nic->aq_hw, |
| &aq_ptp->eth_type_filter); |
| } |
| aq_utils_obj_clear(&aq_nic->flags, AQ_NIC_PTP_DPATH_UP); |
| } |
| |
| if (err) |
| return -EREMOTEIO; |
| |
| aq_ptp->hwtstamp_config = *config; |
| |
| return 0; |
| } |
| |
| bool aq_ptp_ring(struct aq_nic_s *aq_nic, struct aq_ring_s *ring) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| |
| if (!aq_ptp) |
| return false; |
| |
| return &aq_ptp->ptp_tx == ring || |
| &aq_ptp->ptp_rx == ring || &aq_ptp->hwts_rx == ring; |
| } |
| |
| u16 aq_ptp_extract_ts(struct aq_nic_s *aq_nic, struct sk_buff *skb, u8 *p, |
| unsigned int len) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| u64 timestamp = 0; |
| u16 ret = aq_nic->aq_hw_ops->rx_extract_ts(aq_nic->aq_hw, |
| p, len, ×tamp); |
| |
| if (ret > 0) |
| aq_ptp_rx_hwtstamp(aq_ptp, skb, timestamp); |
| |
| return ret; |
| } |
| |
| static int aq_ptp_poll(struct napi_struct *napi, int budget) |
| { |
| struct aq_ptp_s *aq_ptp = container_of(napi, struct aq_ptp_s, napi); |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| bool was_cleaned = false; |
| int work_done = 0; |
| int err; |
| |
| /* Processing PTP TX traffic */ |
| err = aq_nic->aq_hw_ops->hw_ring_tx_head_update(aq_nic->aq_hw, |
| &aq_ptp->ptp_tx); |
| if (err < 0) |
| goto err_exit; |
| |
| if (aq_ptp->ptp_tx.sw_head != aq_ptp->ptp_tx.hw_head) { |
| aq_ring_tx_clean(&aq_ptp->ptp_tx); |
| |
| was_cleaned = true; |
| } |
| |
| /* Processing HW_TIMESTAMP RX traffic */ |
| err = aq_nic->aq_hw_ops->hw_ring_hwts_rx_receive(aq_nic->aq_hw, |
| &aq_ptp->hwts_rx); |
| if (err < 0) |
| goto err_exit; |
| |
| if (aq_ptp->hwts_rx.sw_head != aq_ptp->hwts_rx.hw_head) { |
| aq_ring_hwts_rx_clean(&aq_ptp->hwts_rx, aq_nic); |
| |
| err = aq_nic->aq_hw_ops->hw_ring_hwts_rx_fill(aq_nic->aq_hw, |
| &aq_ptp->hwts_rx); |
| if (err < 0) |
| goto err_exit; |
| |
| was_cleaned = true; |
| } |
| |
| /* Processing PTP RX traffic */ |
| err = aq_nic->aq_hw_ops->hw_ring_rx_receive(aq_nic->aq_hw, |
| &aq_ptp->ptp_rx); |
| if (err < 0) |
| goto err_exit; |
| |
| if (aq_ptp->ptp_rx.sw_head != aq_ptp->ptp_rx.hw_head) { |
| unsigned int sw_tail_old; |
| |
| err = aq_ring_rx_clean(&aq_ptp->ptp_rx, napi, &work_done, budget); |
| if (err < 0) |
| goto err_exit; |
| |
| sw_tail_old = aq_ptp->ptp_rx.sw_tail; |
| err = aq_ring_rx_fill(&aq_ptp->ptp_rx); |
| if (err < 0) |
| goto err_exit; |
| |
| err = aq_nic->aq_hw_ops->hw_ring_rx_fill(aq_nic->aq_hw, |
| &aq_ptp->ptp_rx, |
| sw_tail_old); |
| if (err < 0) |
| goto err_exit; |
| } |
| |
| if (was_cleaned) |
| work_done = budget; |
| |
| if (work_done < budget) { |
| napi_complete_done(napi, work_done); |
| aq_nic->aq_hw_ops->hw_irq_enable(aq_nic->aq_hw, |
| BIT_ULL(aq_ptp->ptp_ring_param.vec_idx)); |
| } |
| |
| err_exit: |
| return work_done; |
| } |
| |
| static irqreturn_t aq_ptp_isr(int irq, void *private) |
| { |
| struct aq_ptp_s *aq_ptp = private; |
| int err = 0; |
| |
| if (!aq_ptp) { |
| err = -EINVAL; |
| goto err_exit; |
| } |
| napi_schedule(&aq_ptp->napi); |
| |
| err_exit: |
| return err >= 0 ? IRQ_HANDLED : IRQ_NONE; |
| } |
| |
| int aq_ptp_xmit(struct aq_nic_s *aq_nic, struct sk_buff *skb) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| struct aq_ring_s *ring = &aq_ptp->ptp_tx; |
| unsigned long irq_flags; |
| int err = NETDEV_TX_OK; |
| unsigned int frags; |
| |
| if (skb->len <= 0) { |
| dev_kfree_skb_any(skb); |
| goto err_exit; |
| } |
| |
| frags = skb_shinfo(skb)->nr_frags + 1; |
| /* Frags cannot be bigger 16KB |
| * because PTP usually works |
| * without Jumbo even in a background |
| */ |
| if (frags > AQ_CFG_SKB_FRAGS_MAX || frags > aq_ring_avail_dx(ring)) { |
| /* Drop packet because it doesn't make sence to delay it */ |
| dev_kfree_skb_any(skb); |
| goto err_exit; |
| } |
| |
| err = aq_ptp_skb_put(&aq_ptp->skb_ring, skb); |
| if (err) { |
| netdev_err(aq_nic->ndev, "SKB Ring is overflow (%u)!\n", |
| ring->size); |
| return NETDEV_TX_BUSY; |
| } |
| skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; |
| aq_ptp_tx_timeout_start(aq_ptp); |
| skb_tx_timestamp(skb); |
| |
| spin_lock_irqsave(&aq_nic->aq_ptp->ptp_ring_lock, irq_flags); |
| frags = aq_nic_map_skb(aq_nic, skb, ring); |
| |
| if (likely(frags)) { |
| err = aq_nic->aq_hw_ops->hw_ring_tx_xmit(aq_nic->aq_hw, |
| ring, frags); |
| if (err >= 0) { |
| u64_stats_update_begin(&ring->stats.tx.syncp); |
| ++ring->stats.tx.packets; |
| ring->stats.tx.bytes += skb->len; |
| u64_stats_update_end(&ring->stats.tx.syncp); |
| } |
| } else { |
| err = NETDEV_TX_BUSY; |
| } |
| spin_unlock_irqrestore(&aq_nic->aq_ptp->ptp_ring_lock, irq_flags); |
| |
| err_exit: |
| return err; |
| } |
| |
| void aq_ptp_service_task(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| |
| if (!aq_ptp) |
| return; |
| |
| aq_ptp_tx_timeout_check(aq_ptp); |
| } |
| |
| int aq_ptp_irq_alloc(struct aq_nic_s *aq_nic) |
| { |
| struct pci_dev *pdev = aq_nic->pdev; |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| int err = 0; |
| |
| if (!aq_ptp) |
| return 0; |
| |
| if (pdev->msix_enabled || pdev->msi_enabled) { |
| err = request_irq(pci_irq_vector(pdev, aq_ptp->idx_vector), |
| aq_ptp_isr, 0, aq_nic->ndev->name, aq_ptp); |
| } else { |
| err = -EINVAL; |
| goto err_exit; |
| } |
| |
| err_exit: |
| return err; |
| } |
| |
| void aq_ptp_irq_free(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| struct pci_dev *pdev = aq_nic->pdev; |
| |
| if (!aq_ptp) |
| return; |
| |
| free_irq(pci_irq_vector(pdev, aq_ptp->idx_vector), aq_ptp); |
| } |
| |
| int aq_ptp_ring_init(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| int err = 0; |
| |
| if (!aq_ptp) |
| return 0; |
| |
| err = aq_ring_init(&aq_ptp->ptp_tx, ATL_RING_TX); |
| if (err < 0) |
| goto err_exit; |
| err = aq_nic->aq_hw_ops->hw_ring_tx_init(aq_nic->aq_hw, |
| &aq_ptp->ptp_tx, |
| &aq_ptp->ptp_ring_param); |
| if (err < 0) |
| goto err_exit; |
| |
| err = aq_ring_init(&aq_ptp->ptp_rx, ATL_RING_RX); |
| if (err < 0) |
| goto err_exit; |
| err = aq_nic->aq_hw_ops->hw_ring_rx_init(aq_nic->aq_hw, |
| &aq_ptp->ptp_rx, |
| &aq_ptp->ptp_ring_param); |
| if (err < 0) |
| goto err_exit; |
| |
| err = aq_ring_rx_fill(&aq_ptp->ptp_rx); |
| if (err < 0) |
| goto err_rx_free; |
| err = aq_nic->aq_hw_ops->hw_ring_rx_fill(aq_nic->aq_hw, |
| &aq_ptp->ptp_rx, |
| 0U); |
| if (err < 0) |
| goto err_rx_free; |
| |
| err = aq_ring_init(&aq_ptp->hwts_rx, ATL_RING_RX); |
| if (err < 0) |
| goto err_rx_free; |
| err = aq_nic->aq_hw_ops->hw_ring_rx_init(aq_nic->aq_hw, |
| &aq_ptp->hwts_rx, |
| &aq_ptp->ptp_ring_param); |
| if (err < 0) |
| goto err_exit; |
| err = aq_nic->aq_hw_ops->hw_ring_hwts_rx_fill(aq_nic->aq_hw, |
| &aq_ptp->hwts_rx); |
| if (err < 0) |
| goto err_exit; |
| |
| return err; |
| |
| err_rx_free: |
| aq_ring_rx_deinit(&aq_ptp->ptp_rx); |
| err_exit: |
| return err; |
| } |
| |
| int aq_ptp_ring_start(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| int err = 0; |
| |
| if (!aq_ptp) |
| return 0; |
| |
| err = aq_nic->aq_hw_ops->hw_ring_tx_start(aq_nic->aq_hw, &aq_ptp->ptp_tx); |
| if (err < 0) |
| goto err_exit; |
| |
| err = aq_nic->aq_hw_ops->hw_ring_rx_start(aq_nic->aq_hw, &aq_ptp->ptp_rx); |
| if (err < 0) |
| goto err_exit; |
| |
| err = aq_nic->aq_hw_ops->hw_ring_rx_start(aq_nic->aq_hw, |
| &aq_ptp->hwts_rx); |
| if (err < 0) |
| goto err_exit; |
| |
| napi_enable(&aq_ptp->napi); |
| |
| err_exit: |
| return err; |
| } |
| |
| void aq_ptp_ring_stop(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| |
| if (!aq_ptp) |
| return; |
| |
| aq_nic->aq_hw_ops->hw_ring_tx_stop(aq_nic->aq_hw, &aq_ptp->ptp_tx); |
| aq_nic->aq_hw_ops->hw_ring_rx_stop(aq_nic->aq_hw, &aq_ptp->ptp_rx); |
| |
| aq_nic->aq_hw_ops->hw_ring_rx_stop(aq_nic->aq_hw, &aq_ptp->hwts_rx); |
| |
| napi_disable(&aq_ptp->napi); |
| } |
| |
| void aq_ptp_ring_deinit(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| |
| if (!aq_ptp || !aq_ptp->ptp_tx.aq_nic || !aq_ptp->ptp_rx.aq_nic) |
| return; |
| |
| aq_ring_tx_clean(&aq_ptp->ptp_tx); |
| aq_ring_rx_deinit(&aq_ptp->ptp_rx); |
| } |
| |
| int aq_ptp_ring_alloc(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| unsigned int tx_ring_idx, rx_ring_idx; |
| struct aq_ring_s *hwts; |
| struct aq_ring_s *ring; |
| int err; |
| |
| if (!aq_ptp) |
| return 0; |
| |
| tx_ring_idx = aq_ptp_ring_idx(aq_nic->aq_nic_cfg.tc_mode); |
| |
| ring = aq_ring_tx_alloc(&aq_ptp->ptp_tx, aq_nic, |
| tx_ring_idx, &aq_nic->aq_nic_cfg); |
| if (!ring) { |
| err = -ENOMEM; |
| goto err_exit; |
| } |
| |
| rx_ring_idx = aq_ptp_ring_idx(aq_nic->aq_nic_cfg.tc_mode); |
| |
| ring = aq_ring_rx_alloc(&aq_ptp->ptp_rx, aq_nic, |
| rx_ring_idx, &aq_nic->aq_nic_cfg); |
| if (!ring) { |
| err = -ENOMEM; |
| goto err_exit_ptp_tx; |
| } |
| |
| hwts = aq_ring_hwts_rx_alloc(&aq_ptp->hwts_rx, aq_nic, PTP_HWST_RING_IDX, |
| aq_nic->aq_nic_cfg.rxds, |
| aq_nic->aq_nic_cfg.aq_hw_caps->rxd_size); |
| if (!hwts) { |
| err = -ENOMEM; |
| goto err_exit_ptp_rx; |
| } |
| |
| err = aq_ptp_skb_ring_init(&aq_ptp->skb_ring, aq_nic->aq_nic_cfg.rxds); |
| if (err != 0) { |
| err = -ENOMEM; |
| goto err_exit_hwts_rx; |
| } |
| |
| aq_ptp->ptp_ring_param.vec_idx = aq_ptp->idx_vector; |
| aq_ptp->ptp_ring_param.cpu = aq_ptp->ptp_ring_param.vec_idx + |
| aq_nic_get_cfg(aq_nic)->aq_rss.base_cpu_number; |
| cpumask_set_cpu(aq_ptp->ptp_ring_param.cpu, |
| &aq_ptp->ptp_ring_param.affinity_mask); |
| |
| return 0; |
| |
| err_exit_hwts_rx: |
| aq_ring_free(&aq_ptp->hwts_rx); |
| err_exit_ptp_rx: |
| aq_ring_free(&aq_ptp->ptp_rx); |
| err_exit_ptp_tx: |
| aq_ring_free(&aq_ptp->ptp_tx); |
| err_exit: |
| return err; |
| } |
| |
| void aq_ptp_ring_free(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| |
| if (!aq_ptp) |
| return; |
| |
| aq_ring_free(&aq_ptp->ptp_tx); |
| aq_ring_free(&aq_ptp->ptp_rx); |
| aq_ring_free(&aq_ptp->hwts_rx); |
| |
| aq_ptp_skb_ring_release(&aq_ptp->skb_ring); |
| } |
| |
| #define MAX_PTP_GPIO_COUNT 4 |
| |
| static struct ptp_clock_info aq_ptp_clock = { |
| .owner = THIS_MODULE, |
| .name = "atlantic ptp", |
| .max_adj = 999999999, |
| .n_ext_ts = 0, |
| .pps = 0, |
| .adjfine = aq_ptp_adjfine, |
| .adjtime = aq_ptp_adjtime, |
| .gettime64 = aq_ptp_gettime, |
| .settime64 = aq_ptp_settime, |
| .n_per_out = 0, |
| .enable = aq_ptp_gpio_feature_enable, |
| .n_pins = 0, |
| .verify = aq_ptp_verify, |
| .pin_config = NULL, |
| }; |
| |
| #define ptp_offset_init(__idx, __mbps, __egress, __ingress) do { \ |
| ptp_offset[__idx].mbps = (__mbps); \ |
| ptp_offset[__idx].egress = (__egress); \ |
| ptp_offset[__idx].ingress = (__ingress); } \ |
| while (0) |
| |
| static void aq_ptp_offset_init_from_fw(const struct hw_atl_ptp_offset *offsets) |
| { |
| int i; |
| |
| /* Load offsets for PTP */ |
| for (i = 0; i < ARRAY_SIZE(ptp_offset); i++) { |
| switch (i) { |
| /* 100M */ |
| case ptp_offset_idx_100: |
| ptp_offset_init(i, 100, |
| offsets->egress_100, |
| offsets->ingress_100); |
| break; |
| /* 1G */ |
| case ptp_offset_idx_1000: |
| ptp_offset_init(i, 1000, |
| offsets->egress_1000, |
| offsets->ingress_1000); |
| break; |
| /* 2.5G */ |
| case ptp_offset_idx_2500: |
| ptp_offset_init(i, 2500, |
| offsets->egress_2500, |
| offsets->ingress_2500); |
| break; |
| /* 5G */ |
| case ptp_offset_idx_5000: |
| ptp_offset_init(i, 5000, |
| offsets->egress_5000, |
| offsets->ingress_5000); |
| break; |
| /* 10G */ |
| case ptp_offset_idx_10000: |
| ptp_offset_init(i, 10000, |
| offsets->egress_10000, |
| offsets->ingress_10000); |
| break; |
| } |
| } |
| } |
| |
| static void aq_ptp_offset_init(const struct hw_atl_ptp_offset *offsets) |
| { |
| memset(ptp_offset, 0, sizeof(ptp_offset)); |
| |
| aq_ptp_offset_init_from_fw(offsets); |
| } |
| |
| static void aq_ptp_gpio_init(struct ptp_clock_info *info, |
| struct hw_atl_info *hw_info) |
| { |
| struct ptp_pin_desc pin_desc[MAX_PTP_GPIO_COUNT]; |
| u32 extts_pin_cnt = 0; |
| u32 out_pin_cnt = 0; |
| u32 i; |
| |
| memset(pin_desc, 0, sizeof(pin_desc)); |
| |
| for (i = 0; i < MAX_PTP_GPIO_COUNT - 1; i++) { |
| if (hw_info->gpio_pin[i] == |
| (GPIO_PIN_FUNCTION_PTP0 + out_pin_cnt)) { |
| snprintf(pin_desc[out_pin_cnt].name, |
| sizeof(pin_desc[out_pin_cnt].name), |
| "AQ_GPIO%d", i); |
| pin_desc[out_pin_cnt].index = out_pin_cnt; |
| pin_desc[out_pin_cnt].chan = out_pin_cnt; |
| pin_desc[out_pin_cnt++].func = PTP_PF_PEROUT; |
| } |
| } |
| |
| info->n_per_out = out_pin_cnt; |
| |
| if (hw_info->caps_ex & BIT(CAPS_EX_PHY_CTRL_TS_PIN)) { |
| extts_pin_cnt += 1; |
| |
| snprintf(pin_desc[out_pin_cnt].name, |
| sizeof(pin_desc[out_pin_cnt].name), |
| "AQ_GPIO%d", out_pin_cnt); |
| pin_desc[out_pin_cnt].index = out_pin_cnt; |
| pin_desc[out_pin_cnt].chan = 0; |
| pin_desc[out_pin_cnt].func = PTP_PF_EXTTS; |
| } |
| |
| info->n_pins = out_pin_cnt + extts_pin_cnt; |
| info->n_ext_ts = extts_pin_cnt; |
| |
| if (!info->n_pins) |
| return; |
| |
| info->pin_config = kcalloc(info->n_pins, sizeof(struct ptp_pin_desc), |
| GFP_KERNEL); |
| |
| if (!info->pin_config) |
| return; |
| |
| memcpy(info->pin_config, &pin_desc, |
| sizeof(struct ptp_pin_desc) * info->n_pins); |
| } |
| |
| void aq_ptp_clock_init(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| struct timespec64 ts; |
| |
| ktime_get_real_ts64(&ts); |
| aq_ptp_settime(&aq_ptp->ptp_info, &ts); |
| } |
| |
| static void aq_ptp_poll_sync_work_cb(struct work_struct *w); |
| |
| int aq_ptp_init(struct aq_nic_s *aq_nic, unsigned int idx_vec) |
| { |
| bool a1_ptp = ATL_HW_IS_CHIP_FEATURE(aq_nic->aq_hw, ATLANTIC); |
| struct hw_atl_utils_mbox mbox; |
| struct ptp_clock *clock; |
| struct aq_ptp_s *aq_ptp; |
| int err = 0; |
| |
| if (!a1_ptp) { |
| aq_nic->aq_ptp = NULL; |
| return 0; |
| } |
| |
| if (!aq_nic->aq_hw_ops->hw_get_ptp_ts) { |
| aq_nic->aq_ptp = NULL; |
| return 0; |
| } |
| |
| if (!aq_nic->aq_fw_ops->enable_ptp) { |
| aq_nic->aq_ptp = NULL; |
| return 0; |
| } |
| |
| hw_atl_utils_mpi_read_stats(aq_nic->aq_hw, &mbox); |
| |
| if (!(mbox.info.caps_ex & BIT(CAPS_EX_PHY_PTP_EN))) { |
| aq_nic->aq_ptp = NULL; |
| return 0; |
| } |
| |
| aq_ptp_offset_init(&mbox.info.ptp_offset); |
| |
| aq_ptp = kzalloc(sizeof(*aq_ptp), GFP_KERNEL); |
| if (!aq_ptp) { |
| err = -ENOMEM; |
| goto err_exit; |
| } |
| |
| aq_ptp->aq_nic = aq_nic; |
| aq_ptp->a1_ptp = a1_ptp; |
| |
| spin_lock_init(&aq_ptp->ptp_lock); |
| spin_lock_init(&aq_ptp->ptp_ring_lock); |
| |
| aq_ptp->ptp_info = aq_ptp_clock; |
| aq_ptp_gpio_init(&aq_ptp->ptp_info, &mbox.info); |
| clock = ptp_clock_register(&aq_ptp->ptp_info, &aq_nic->ndev->dev); |
| if (IS_ERR(clock)) { |
| netdev_err(aq_nic->ndev, "ptp_clock_register failed\n"); |
| err = PTR_ERR(clock); |
| goto err_exit; |
| } |
| aq_ptp->ptp_clock = clock; |
| aq_ptp_tx_timeout_init(&aq_ptp->ptp_tx_timeout); |
| |
| atomic_set(&aq_ptp->offset_egress, 0); |
| atomic_set(&aq_ptp->offset_ingress, 0); |
| |
| netif_napi_add(aq_nic_get_ndev(aq_nic), &aq_ptp->napi, |
| aq_ptp_poll, AQ_CFG_NAPI_WEIGHT); |
| |
| aq_ptp->idx_vector = idx_vec; |
| |
| aq_nic->aq_ptp = aq_ptp; |
| |
| /* enable ptp counter */ |
| aq_utils_obj_set(&aq_nic->aq_hw->flags, AQ_HW_PTP_AVAILABLE); |
| mutex_lock(&aq_nic->fwreq_mutex); |
| aq_nic->aq_fw_ops->enable_ptp(aq_nic->aq_hw, 1); |
| aq_ptp_clock_init(aq_nic); |
| mutex_unlock(&aq_nic->fwreq_mutex); |
| |
| INIT_DELAYED_WORK(&aq_ptp->poll_sync, &aq_ptp_poll_sync_work_cb); |
| aq_ptp->eth_type_filter.location = |
| aq_nic_reserve_filter(aq_nic, aq_rx_filter_ethertype); |
| aq_ptp->udp_filter.location = |
| aq_nic_reserve_filter(aq_nic, aq_rx_filter_l3l4); |
| |
| return 0; |
| |
| err_exit: |
| if (aq_ptp) |
| kfree(aq_ptp->ptp_info.pin_config); |
| kfree(aq_ptp); |
| aq_nic->aq_ptp = NULL; |
| return err; |
| } |
| |
| void aq_ptp_unregister(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| |
| if (!aq_ptp) |
| return; |
| |
| ptp_clock_unregister(aq_ptp->ptp_clock); |
| } |
| |
| void aq_ptp_free(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| |
| if (!aq_ptp) |
| return; |
| |
| aq_nic_release_filter(aq_nic, aq_rx_filter_ethertype, |
| aq_ptp->eth_type_filter.location); |
| aq_nic_release_filter(aq_nic, aq_rx_filter_l3l4, |
| aq_ptp->udp_filter.location); |
| cancel_delayed_work_sync(&aq_ptp->poll_sync); |
| /* disable ptp */ |
| mutex_lock(&aq_nic->fwreq_mutex); |
| aq_nic->aq_fw_ops->enable_ptp(aq_nic->aq_hw, 0); |
| mutex_unlock(&aq_nic->fwreq_mutex); |
| |
| kfree(aq_ptp->ptp_info.pin_config); |
| |
| netif_napi_del(&aq_ptp->napi); |
| kfree(aq_ptp); |
| aq_nic->aq_ptp = NULL; |
| } |
| |
| struct ptp_clock *aq_ptp_get_ptp_clock(struct aq_ptp_s *aq_ptp) |
| { |
| return aq_ptp->ptp_clock; |
| } |
| |
| /* PTP external GPIO nanoseconds count */ |
| static uint64_t aq_ptp_get_sync1588_ts(struct aq_nic_s *aq_nic) |
| { |
| u64 ts = 0; |
| |
| if (aq_nic->aq_hw_ops->hw_get_sync_ts) |
| aq_nic->aq_hw_ops->hw_get_sync_ts(aq_nic->aq_hw, &ts); |
| |
| return ts; |
| } |
| |
| static void aq_ptp_start_work(struct aq_ptp_s *aq_ptp) |
| { |
| if (aq_ptp->extts_pin_enabled) { |
| aq_ptp->poll_timeout_ms = POLL_SYNC_TIMER_MS; |
| aq_ptp->last_sync1588_ts = |
| aq_ptp_get_sync1588_ts(aq_ptp->aq_nic); |
| schedule_delayed_work(&aq_ptp->poll_sync, |
| msecs_to_jiffies(aq_ptp->poll_timeout_ms)); |
| } |
| } |
| |
| int aq_ptp_link_change(struct aq_nic_s *aq_nic) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| |
| if (!aq_ptp) |
| return 0; |
| |
| if (aq_nic->aq_hw->aq_link_status.mbps) |
| aq_ptp_start_work(aq_ptp); |
| else |
| cancel_delayed_work_sync(&aq_ptp->poll_sync); |
| |
| return 0; |
| } |
| |
| static bool aq_ptp_sync_ts_updated(struct aq_ptp_s *aq_ptp, u64 *new_ts) |
| { |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| u64 sync_ts2; |
| u64 sync_ts; |
| |
| sync_ts = aq_ptp_get_sync1588_ts(aq_nic); |
| |
| if (sync_ts != aq_ptp->last_sync1588_ts) { |
| sync_ts2 = aq_ptp_get_sync1588_ts(aq_nic); |
| if (sync_ts != sync_ts2) { |
| sync_ts = sync_ts2; |
| sync_ts2 = aq_ptp_get_sync1588_ts(aq_nic); |
| if (sync_ts != sync_ts2) { |
| netdev_err(aq_nic->ndev, |
| "%s: Unable to get correct GPIO TS", |
| __func__); |
| sync_ts = 0; |
| } |
| } |
| |
| *new_ts = sync_ts; |
| return true; |
| } |
| return false; |
| } |
| |
| static int aq_ptp_check_sync1588(struct aq_ptp_s *aq_ptp) |
| { |
| struct aq_nic_s *aq_nic = aq_ptp->aq_nic; |
| u64 sync_ts; |
| |
| /* Sync1588 pin was triggered */ |
| if (aq_ptp_sync_ts_updated(aq_ptp, &sync_ts)) { |
| if (aq_ptp->extts_pin_enabled) { |
| struct ptp_clock_event ptp_event; |
| u64 time = 0; |
| |
| aq_nic->aq_hw_ops->hw_ts_to_sys_clock(aq_nic->aq_hw, |
| sync_ts, &time); |
| ptp_event.index = aq_ptp->ptp_info.n_pins - 1; |
| ptp_event.timestamp = time; |
| |
| ptp_event.type = PTP_CLOCK_EXTTS; |
| ptp_clock_event(aq_ptp->ptp_clock, &ptp_event); |
| } |
| |
| aq_ptp->last_sync1588_ts = sync_ts; |
| } |
| |
| return 0; |
| } |
| |
| static void aq_ptp_poll_sync_work_cb(struct work_struct *w) |
| { |
| struct delayed_work *dw = to_delayed_work(w); |
| struct aq_ptp_s *aq_ptp = container_of(dw, struct aq_ptp_s, poll_sync); |
| |
| aq_ptp_check_sync1588(aq_ptp); |
| |
| if (aq_ptp->extts_pin_enabled) { |
| unsigned long timeout = msecs_to_jiffies(aq_ptp->poll_timeout_ms); |
| |
| schedule_delayed_work(&aq_ptp->poll_sync, timeout); |
| } |
| } |
| |
| int aq_ptp_get_ring_cnt(struct aq_nic_s *aq_nic, const enum atl_ring_type ring_type) |
| { |
| if (!aq_nic->aq_ptp) |
| return 0; |
| |
| /* Additional RX ring is allocated for PTP HWTS on A1 */ |
| return (aq_nic->aq_ptp->a1_ptp && ring_type == ATL_RING_RX) ? 2 : 1; |
| } |
| |
| u64 *aq_ptp_get_stats(struct aq_nic_s *aq_nic, u64 *data) |
| { |
| struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp; |
| unsigned int count = 0U; |
| |
| if (!aq_ptp) |
| return data; |
| |
| count = aq_ring_fill_stats_data(&aq_ptp->ptp_rx, data); |
| data += count; |
| count = aq_ring_fill_stats_data(&aq_ptp->ptp_tx, data); |
| data += count; |
| |
| if (aq_ptp->a1_ptp) { |
| /* Only Receive ring for HWTS */ |
| count = aq_ring_fill_stats_data(&aq_ptp->hwts_rx, data); |
| data += count; |
| } |
| |
| return data; |
| } |
| |
| #endif |