| /* |
| * drivers/net/imx_ptp.c |
| * |
| * Copyright (C) 2011 Freescale Semiconductor, Inc. All rights reserved. |
| * |
| * Description: IEEE 1588 driver supporting imx5 Fast Ethernet Controller. |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/kernel.h> |
| #include <linux/delay.h> |
| #include <linux/ioport.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/smp_lock.h> |
| #include <linux/proc_fs.h> |
| #include <linux/errno.h> |
| #include <linux/init.h> |
| #include <linux/reboot.h> |
| #include <linux/list.h> |
| #include <linux/interrupt.h> |
| #include <linux/device.h> |
| #include <linux/workqueue.h> |
| #include <linux/time.h> |
| #include <linux/clk.h> |
| #include <linux/platform_device.h> |
| #include <linux/vmalloc.h> |
| #include <linux/ip.h> |
| #include <linux/udp.h> |
| #include <linux/io.h> |
| #include <linux/uaccess.h> |
| #include <asm/irq.h> |
| #include <asm/system.h> |
| #include <asm/byteorder.h> |
| #include <asm/unaligned.h> |
| |
| #include "fec.h" |
| #include "fec_1588.h" |
| #include "imx_ptp.h" |
| |
| #ifdef PTP_DEBUG |
| #define VDBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt "\n", \ |
| __func__, ## args) |
| #else |
| #define VDBG(fmt, args...) do {} while (0) |
| #endif |
| |
| static DECLARE_WAIT_QUEUE_HEAD(ptp_rx_ts_wait); |
| static DECLARE_WAIT_QUEUE_HEAD(ptp_tx_ts_wait); |
| #define PTP_GET_RX_TIMEOUT (HZ/10) |
| #define PTP_GET_TX_TIMEOUT (HZ/100) |
| |
| static struct fec_ptp_private *ptp_private; |
| static void ptp_rtc_get_current_time(struct ptp *p_ptp, |
| struct ptp_time *p_time); |
| static struct ptp *ptp_dev; |
| |
| /* The ring resource create and manage */ |
| static int fec_ptp_init_circ(struct circ_buf *ptp_buf, int size) |
| { |
| ptp_buf->buf = vmalloc(size * sizeof(struct fec_ptp_data_t)); |
| |
| if (!ptp_buf->buf) |
| return 1; |
| ptp_buf->head = 0; |
| ptp_buf->tail = 0; |
| |
| return 0; |
| } |
| |
| static inline int fec_ptp_calc_index(int size, int curr_index, int offset) |
| { |
| return (curr_index + offset) % size; |
| } |
| |
| static int fec_ptp_is_empty(struct circ_buf *buf) |
| { |
| return (buf->head == buf->tail); |
| } |
| |
| static int fec_ptp_nelems(struct circ_buf *buf, int size) |
| { |
| const int front = buf->head; |
| const int end = buf->tail; |
| int n_items; |
| |
| if (end > front) |
| n_items = end - front; |
| else if (end < front) |
| n_items = size - (front - end); |
| else |
| n_items = 0; |
| |
| return n_items; |
| } |
| |
| static int fec_ptp_is_full(struct circ_buf *buf, int size) |
| { |
| if (fec_ptp_nelems(buf, size) == (size - 1)) |
| return 1; |
| else |
| return 0; |
| } |
| |
| static int fec_ptp_insert(struct circ_buf *ptp_buf, |
| struct fec_ptp_data_t *data, |
| struct fec_ptp_private *priv, |
| int size) |
| { |
| struct fec_ptp_data_t *tmp; |
| |
| if (fec_ptp_is_full(ptp_buf, size)) |
| return 1; |
| |
| spin_lock(&priv->ptp_lock); |
| tmp = (struct fec_ptp_data_t *)(ptp_buf->buf) + ptp_buf->tail; |
| |
| tmp->key = data->key; |
| memcpy(tmp->spid, data->spid, 10); |
| tmp->ts_time.sec = data->ts_time.sec; |
| tmp->ts_time.nsec = data->ts_time.nsec; |
| |
| ptp_buf->tail = fec_ptp_calc_index(size, ptp_buf->tail, 1); |
| spin_unlock(&priv->ptp_lock); |
| |
| return 0; |
| } |
| |
| static int fec_ptp_find_and_remove(struct circ_buf *ptp_buf, |
| struct fec_ptp_data_t *data, |
| struct fec_ptp_private *priv, |
| int size) |
| { |
| int i; |
| int end = ptp_buf->tail; |
| unsigned long flags; |
| struct fec_ptp_data_t *tmp; |
| |
| if (fec_ptp_is_empty(ptp_buf)) |
| return 1; |
| |
| i = ptp_buf->head; |
| tmp = (struct fec_ptp_data_t *)(ptp_buf->buf) + i; |
| while (i != end) { |
| tmp = (struct fec_ptp_data_t *)(ptp_buf->buf) + i; |
| if (tmp->key == data->key && |
| !memcmp(tmp->spid, data->spid, 10)) |
| break; |
| i = fec_ptp_calc_index(size, i, 1); |
| } |
| |
| spin_lock_irqsave(&priv->ptp_lock, flags); |
| if (i == end) { |
| ptp_buf->head = end; |
| spin_unlock_irqrestore(&priv->ptp_lock, flags); |
| return 1; |
| } |
| |
| data->ts_time.sec = tmp->ts_time.sec; |
| data->ts_time.nsec = tmp->ts_time.nsec; |
| |
| ptp_buf->head = fec_ptp_calc_index(size, i, 1); |
| spin_unlock_irqrestore(&priv->ptp_lock, flags); |
| |
| return 0; |
| } |
| |
| /* ptp and rtc param configuration */ |
| static void rtc_default_param(struct ptp_rtc *rtc) |
| { |
| struct ptp_rtc_driver_param *drv_param = rtc->driver_param; |
| int i; |
| |
| rtc->bypass_compensation = DEFAULT_BYPASS_COMPENSATION; |
| rtc->output_clock_divisor = DEFAULT_OUTPUT_CLOCK_DIVISOR; |
| |
| drv_param->src_clock = DEFAULT_SRC_CLOCK; |
| drv_param->src_clock_freq_hz = clk_get_rate(rtc->clk); |
| |
| drv_param->invert_input_clk_phase = DEFAULT_INVERT_INPUT_CLK_PHASE; |
| drv_param->invert_output_clk_phase = DEFAULT_INVERT_OUTPUT_CLK_PHASE; |
| drv_param->pulse_start_mode = DEFAULT_PULSE_START_MODE; |
| drv_param->events_mask = DEFAULT_EVENTS_RTC_MASK; |
| |
| for (i = 0; i < PTP_RTC_NUM_OF_ALARMS; i++) |
| drv_param->alarm_polarity[i] = DEFAULT_ALARM_POLARITY ; |
| |
| for (i = 0; i < PTP_RTC_NUM_OF_TRIGGERS; i++) |
| drv_param->trigger_polarity[i] = DEFAULT_TRIGGER_POLARITY; |
| } |
| |
| static int ptp_rtc_config(struct ptp_rtc *rtc) |
| { |
| /*allocate memory for RTC driver parameter*/ |
| rtc->driver_param = kzalloc(sizeof(struct ptp_rtc_driver_param), |
| GFP_KERNEL); |
| if (!rtc->driver_param) { |
| printk(KERN_ERR "allocate memory failed\n"); |
| return -ENOMEM; |
| } |
| |
| /* expected RTC input clk frequency */ |
| rtc->driver_param->rtc_freq_hz = PTP_RTC_FREQ * MHZ; |
| |
| /*set default RTC configuration parameters*/ |
| rtc_default_param(rtc); |
| |
| return 0; |
| } |
| |
| static void ptp_param_config(struct ptp *p_ptp) |
| { |
| struct ptp_driver_param *drv_param; |
| |
| p_ptp->driver_param = kzalloc(sizeof(struct ptp_driver_param), |
| GFP_KERNEL); |
| if (!p_ptp->driver_param) { |
| printk(KERN_ERR "allocate memory failed for " |
| "PTP driver parameters\n"); |
| return; |
| } |
| |
| drv_param = p_ptp->driver_param; |
| /*set the default configuration parameters*/ |
| drv_param->eth_type_value = ETH_TYPE_VALUE; |
| drv_param->vlan_type_value = VLAN_TYPE_VALUE; |
| drv_param->udp_general_port = UDP_GENERAL_PORT; |
| drv_param->udp_event_port = UDP_EVENT_PORT; |
| drv_param->ip_type_value = IP_TYPE_VALUE; |
| drv_param->eth_type_offset = ETH_TYPE_OFFSET; |
| drv_param->ip_type_offset = IP_TYPE_OFFSET; |
| drv_param->udp_dest_port_offset = UDP_DEST_PORT_OFFSET; |
| drv_param->ptp_type_offset = PTP_TYPE_OFFSET; |
| |
| drv_param->ptp_msg_codes[e_PTP_MSG_SYNC] = DEFAULT_MSG_SYNC; |
| drv_param->ptp_msg_codes[e_PTP_MSG_DELAY_REQ] = DEFAULT_MSG_DELAY_REQ; |
| drv_param->ptp_msg_codes[e_PTP_MSG_FOLLOW_UP] = DEFAULT_MSG_FOLLOW_UP; |
| drv_param->ptp_msg_codes[e_PTP_MSG_DELAY_RESP] = DEFAULT_MSG_DELAY_RESP; |
| drv_param->ptp_msg_codes[e_PTP_MSG_MANAGEMENT] = DEFAULT_MSG_MANAGEMENT; |
| } |
| |
| /* 64 bits operation */ |
| static u32 div64_oper(u64 dividend, u32 divisor, u32 *quotient) |
| { |
| u32 time_h, time_l; |
| u32 result; |
| u64 tmp_dividend; |
| int i; |
| |
| time_h = (u32)(dividend >> 32); |
| time_l = (u32)dividend; |
| time_h = time_h % divisor; |
| for (i = 1; i <= 32; i++) { |
| tmp_dividend = (((u64)time_h << 32) | (u64)time_l); |
| tmp_dividend = (tmp_dividend << 1); |
| time_h = (u32)(tmp_dividend >> 32); |
| time_l = (u32)tmp_dividend; |
| result = time_h / divisor; |
| time_h = time_h % divisor; |
| *quotient += (result << (32 - i)); |
| } |
| |
| return time_h; |
| } |
| |
| /*64 bites add and return the result*/ |
| static u64 add64_oper(u64 addend, u64 augend) |
| { |
| u64 result = 0; |
| u32 addendh, addendl, augendl, augendh; |
| |
| addendh = (u32)(addend >> 32); |
| addendl = (u32)addend; |
| |
| augendh = (u32)(augend>>32); |
| augendl = (u32)augend; |
| |
| __asm__( |
| "adds %0,%2,%3\n" |
| "adc %1,%4,%5" |
| : "=r" (addendl), "=r" (addendh) |
| : "r" (addendl), "r" (augendl), "r" (addendh), "r" (augendh) |
| ); |
| |
| udelay(1); |
| result = (((u64)addendh << 32) | (u64)addendl); |
| |
| return result; |
| } |
| |
| /*64 bits multiplication and return the result*/ |
| static u64 multi64_oper(u32 multiplier, u32 multiplicand) |
| { |
| u64 result = 0; |
| u64 tmp_ret = 0; |
| u32 tmp_multi = multiplicand; |
| int i; |
| |
| for (i = 0; i < 32; i++) { |
| if (tmp_multi & 0x1) { |
| tmp_ret = ((u64)multiplier << i); |
| result = add64_oper(result, tmp_ret); |
| } |
| tmp_multi = (tmp_multi >> 1); |
| } |
| |
| VDBG("multi 64 low result is 0x%x\n", result); |
| VDBG("multi 64 high result is 0x%x\n", (u32)(result>>32)); |
| |
| return result; |
| } |
| |
| /*convert the 64 bites time stamp to second and nanosecond*/ |
| static void convert_rtc_time(u64 *rtc_time, struct ptp_time *p_time) |
| { |
| u32 time_h; |
| u32 time_sec = 0; |
| |
| time_h = div64_oper(*rtc_time, NANOSEC_IN_SEC, &time_sec); |
| |
| p_time->sec = time_sec; |
| p_time->nsec = time_h; |
| } |
| |
| /* convert rtc time to 64 bites timestamp */ |
| static u64 convert_unsigned_time(struct ptp_time *ptime) |
| { |
| return add64_oper(multi64_oper(ptime->sec, NANOSEC_IN_SEC), |
| (u64)ptime->nsec); |
| } |
| |
| /*RTC interrupt handler*/ |
| static irqreturn_t ptp_rtc_interrupt(int irq, void *_ptp) |
| { |
| struct ptp *p_ptp = (struct ptp *)_ptp; |
| struct ptp_rtc *rtc = p_ptp->rtc; |
| struct ptp_time time; |
| register u32 events; |
| |
| /*get valid events*/ |
| events = readl(rtc->mem_map + PTP_TMR_TEVENT); |
| |
| /*clear event bits*/ |
| writel(events, rtc->mem_map + PTP_TMR_TEVENT); |
| |
| /*get the current time as quickly as possible*/ |
| ptp_rtc_get_current_time(p_ptp, &time); |
| |
| if (events & RTC_TEVENT_ALARM_1) { |
| p_ptp->alarm_counters[0]++; |
| VDBG("PTP Alarm 1 event, time = %2d:%09d[sec:nsec]\n", |
| time.sec, time.nsec); |
| } |
| if (events & RTC_TEVENT_ALARM_2) { |
| p_ptp->alarm_counters[1]++; |
| VDBG("PTP Alarm 2 event, time = %2d:%09d[sec:nsec]\n", |
| time.sec, time.nsec); |
| } |
| if (events & RTC_TEVENT_PERIODIC_PULSE_1) { |
| p_ptp->pulse_counters[0]++; |
| VDBG("PTP Pulse 1 event, time = %2d:%09d[sec:nsec]\n", |
| time.sec, time.nsec); |
| } |
| if (events & RTC_TEVENT_PERIODIC_PULSE_2) { |
| p_ptp->pulse_counters[1]++; |
| VDBG("PTP Pulse 2 event, time = %2d:%09d[sec:nsec]\n", |
| time.sec, time.nsec); |
| } |
| if (events & RTC_TEVENT_PERIODIC_PULSE_3) { |
| p_ptp->pulse_counters[2]++; |
| VDBG("PTP Pulse 3 event, time = %2d:%09d[sec:nsec]\n", |
| time.sec, time.nsec); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int ptp_rtc_init(struct ptp *p_ptp) |
| { |
| struct ptp_rtc *rtc = p_ptp->rtc; |
| struct ptp_rtc_driver_param *rtc_drv_param = rtc->driver_param; |
| void __iomem *rtc_mem = rtc->mem_map; |
| u32 freq_compensation = 0; |
| u32 tmr_ctrl = 0; |
| int ret = 0; |
| int i; |
| |
| rtc = p_ptp->rtc; |
| rtc_drv_param = rtc->driver_param; |
| rtc_mem = rtc->mem_map; |
| |
| if (!rtc->bypass_compensation) |
| rtc->clock_period_nansec = NANOSEC_PER_ONE_HZ_TICK / |
| rtc_drv_param->rtc_freq_hz; |
| else { |
| /*In bypass mode,the RTC clock equals the source clock*/ |
| rtc->clock_period_nansec = NANOSEC_PER_ONE_HZ_TICK / |
| rtc_drv_param->src_clock_freq_hz; |
| tmr_ctrl |= RTC_TMR_CTRL_BYP; |
| } |
| |
| tmr_ctrl |= ((rtc->clock_period_nansec << |
| RTC_TMR_CTRL_TCLK_PERIOD_SHIFT) & |
| RTC_TMR_CTRL_TCLK_PERIOD_MSK); |
| |
| if (rtc_drv_param->invert_input_clk_phase) |
| tmr_ctrl |= RTC_TMR_CTRL_CIPH; |
| if (rtc_drv_param->invert_output_clk_phase) |
| tmr_ctrl |= RTC_TMR_CTRL_COPH; |
| if (rtc_drv_param->pulse_start_mode == e_PTP_RTC_PULSE_START_ON_ALARM) { |
| tmr_ctrl |= RTC_TMR_CTRL_FS; |
| rtc->start_pulse_on_alarm = TRUE; |
| } |
| |
| for (i = 0; i < PTP_RTC_NUM_OF_ALARMS; i++) { |
| if (rtc_drv_param->alarm_polarity[i] == |
| e_PTP_RTC_ALARM_POLARITY_ACTIVE_LOW) |
| tmr_ctrl |= (RTC_TMR_CTRL_ALMP1 >> i); |
| |
| } |
| |
| /*clear TMR_ALARM registers*/ |
| writel(0xFFFFFFFF, rtc_mem + PTP_TMR_ALARM1_L); |
| writel(0xFFFFFFFF, rtc_mem + PTP_TMR_ALARM1_H); |
| writel(0xFFFFFFFF, rtc_mem + PTP_TMR_ALARM2_L); |
| writel(0xFFFFFFFF, rtc_mem + PTP_TMR_ALARM2_H); |
| |
| for (i = 0; i < PTP_RTC_NUM_OF_TRIGGERS; i++) { |
| if (rtc_drv_param->trigger_polarity[i] == |
| e_PTP_RTC_TRIGGER_ON_FALLING_EDGE) |
| tmr_ctrl |= (RTC_TMR_CTRL_ETEP1 << i); |
| } |
| |
| /*clear TMR_FIPER registers*/ |
| writel(0xFFFFFFFF, rtc_mem + PTP_TMR_FIPER1); |
| writel(0xFFFFFFFF, rtc_mem + PTP_TMR_FIPER2); |
| writel(0xFFFFFFFF, rtc_mem + PTP_TMR_FIPER3); |
| |
| /*set the source clock*/ |
| /*use a clock from the QE bank of clocks*/ |
| tmr_ctrl |= RTC_TMR_CTRL_CKSEL_EXT_CLK; |
| |
| /*write register and perform software reset*/ |
| writel((tmr_ctrl | RTC_TMR_CTRL_TMSR), rtc_mem + PTP_TMR_CTRL); |
| writel(tmr_ctrl, rtc_mem + PTP_TMR_CTRL); |
| |
| /*clear TMR_TEVEMT*/ |
| writel(RTC_EVENT_ALL, rtc_mem + PTP_TMR_TEVENT); |
| |
| /*initialize TMR_TEMASK*/ |
| writel(rtc_drv_param->events_mask, rtc_mem + PTP_TMR_TEMASK); |
| |
| /*initialize TMR_ADD with the initial frequency compensation value: |
| freq_compensation = (2^32 / frequency ratio)*/ |
| div64_oper(((u64)rtc_drv_param->rtc_freq_hz << 32), |
| rtc_drv_param->src_clock_freq_hz, &freq_compensation); |
| p_ptp->orig_freq_comp = freq_compensation; |
| writel(freq_compensation, rtc_mem + PTP_TMR_ADD); |
| |
| /*initialize TMR_PRSC*/ |
| writel(rtc->output_clock_divisor, rtc_mem + PTP_TMR_PRSC); |
| |
| /*initialize TMR_OFF*/ |
| writel(0, rtc_mem + PTP_TMR_OFF_L); |
| writel(0, rtc_mem + PTP_TMR_OFF_H); |
| |
| return ret; |
| } |
| |
| static void init_ptp_parser(struct ptp *p_ptp) |
| { |
| void __iomem *mem_map = p_ptp->mem_map; |
| struct ptp_driver_param *drv_param = p_ptp->driver_param; |
| u32 reg32; |
| |
| /*initialzie PTP TSPDR1*/ |
| reg32 = ((drv_param->eth_type_value << PTP_TSPDR1_ETT_SHIFT) & |
| PTP_TSPDR1_ETT_MASK); |
| reg32 |= ((drv_param->ip_type_value << PTP_TSPDR1_IPT_SHIFT) & |
| PTP_TSPDR1_IPT_MASK); |
| writel(reg32, mem_map + PTP_TSPDR1); |
| |
| /*initialize PTP TSPDR2*/ |
| reg32 = ((drv_param->udp_general_port << PTP_TSPDR2_DPNGE_SHIFT) & |
| PTP_TSPDR2_DPNGE_MASK); |
| reg32 |= (drv_param->udp_event_port & PTP_TSPDR2_DPNEV_MASK); |
| writel(reg32, mem_map + PTP_TSPDR2); |
| |
| /*initialize PTP TSPDR3*/ |
| reg32 = ((drv_param->ptp_msg_codes[e_PTP_MSG_SYNC] << |
| PTP_TSPDR3_SYCTL_SHIFT) & PTP_TSPDR3_SYCTL_MASK); |
| reg32 |= ((drv_param->ptp_msg_codes[e_PTP_MSG_DELAY_REQ] << |
| PTP_TSPDR3_DRCTL_SHIFT) & PTP_TSPDR3_DRCTL_MASK); |
| reg32 |= ((drv_param->ptp_msg_codes[e_PTP_MSG_DELAY_RESP] << |
| PTP_TSPDR3_DRPCTL_SHIFT) & PTP_TSPDR3_DRPCTL_MASK); |
| reg32 |= (drv_param->ptp_msg_codes[e_PTP_MSG_FOLLOW_UP] & |
| PTP_TSPDR3_FUCTL_MASK); |
| writel(reg32, mem_map + PTP_TSPDR3); |
| |
| /*initialzie PTP TSPDR4*/ |
| reg32 = ((drv_param->ptp_msg_codes[e_PTP_MSG_MANAGEMENT] << |
| PTP_TSPDR4_MACTL_SHIFT) & PTP_TSPDR4_MACTL_MASK); |
| reg32 |= (drv_param->vlan_type_value & PTP_TSPDR4_VLAN_MASK); |
| writel(reg32, mem_map + PTP_TSPDR4); |
| |
| /*initialize PTP TSPOV*/ |
| reg32 = ((drv_param->eth_type_offset << PTP_TSPOV_ETTOF_SHIFT) & |
| PTP_TSPOV_ETTOF_MASK); |
| reg32 |= ((drv_param->ip_type_offset << PTP_TSPOV_IPTOF_SHIFT) & |
| PTP_TSPOV_IPTOF_MASK); |
| reg32 |= ((drv_param->udp_dest_port_offset << PTP_TSPOV_UDOF_SHIFT) & |
| PTP_TSPOV_UDOF_MASK); |
| reg32 |= (drv_param->ptp_type_offset & PTP_TSPOV_PTOF_MASK); |
| writel(reg32, mem_map + PTP_TSPOV); |
| } |
| |
| /* compatible with MXS 1588 */ |
| #ifdef CONFIG_IN_BAND |
| void fec_ptp_store_txstamp(struct fec_ptp_private *priv, |
| struct sk_buff *skb, |
| struct bufdesc *bdp) |
| { |
| int msg_type, seq_id, control; |
| struct fec_ptp_data_t tmp_tx_time, tmp; |
| struct fec_ptp_private *fpp = priv; |
| struct ptp *p_ptp = ptp_dev; |
| int flag; |
| unsigned char *sp_id; |
| unsigned short portnum; |
| u64 timestamp; |
| |
| /* Check for PTP Event */ |
| if ((bdp->cbd_sc & BD_ENET_TX_PTP) == 0) |
| return; |
| |
| /* Get ts from tx ts queue */ |
| memset(&tmp, 0, sizeof(struct fec_ptp_data_t)); |
| tmp.key = SEQ_ID_OUT_OF_BAND; |
| flag = fec_ptp_find_and_remove(&(priv->txstamp), &tmp, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| if (!flag) { |
| tmp_tx_time.ts_time.sec = tmp.ts_time.sec; |
| tmp_tx_time.ts_time.nsec = tmp.ts_time.nsec; |
| } else { |
| /*read timestamp from register*/ |
| timestamp = ((u64)readl(p_ptp->mem_map + PTP_TMR_TXTS_H) |
| << 32) | |
| (readl(p_ptp->mem_map + PTP_TMR_TXTS_L)); |
| convert_rtc_time(×tamp, &(tmp_tx_time.ts_time)); |
| } |
| |
| seq_id = *((u16 *)(skb->data + FEC_PTP_SEQ_ID_OFFS)); |
| control = *((u8 *)(skb->data + FEC_PTP_CTRL_OFFS)); |
| sp_id = skb->data + FEC_PTP_SPORT_ID_OFFS; |
| portnum = ntohs(*((unsigned short *)(sp_id + 8))); |
| |
| tmp_tx_time.key = ntohs(seq_id); |
| memcpy(tmp_tx_time.spid, sp_id, 8); |
| memcpy(tmp_tx_time.spid + 8, (unsigned char *)&portnum, 2); |
| |
| switch (control) { |
| |
| case PTP_MSG_SYNC: |
| fec_ptp_insert(&(priv->tx_time_sync), &tmp_tx_time, priv, |
| DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| |
| case PTP_MSG_DEL_REQ: |
| fec_ptp_insert(&(priv->tx_time_del_req), &tmp_tx_time, priv, |
| DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| |
| /* clear transportSpecific field*/ |
| case PTP_MSG_ALL_OTHER: |
| msg_type = (*((u8 *)(skb->data + |
| FEC_PTP_MSG_TYPE_OFFS))) & 0x0F; |
| switch (msg_type) { |
| case PTP_MSG_P_DEL_REQ: |
| fec_ptp_insert(&(priv->tx_time_pdel_req), &tmp_tx_time, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| case PTP_MSG_P_DEL_RESP: |
| fec_ptp_insert(&(priv->tx_time_pdel_resp), &tmp_tx_time, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| wake_up_interruptible(&ptp_tx_ts_wait); |
| } |
| #else |
| void fec_ptp_store_txstamp(struct fec_ptp_private *priv, |
| struct sk_buff *skb, |
| struct bufdesc *bdp) |
| { |
| } |
| #endif |
| |
| static void ptp_store_txstamp(struct fec_ptp_private *priv, |
| struct ptp *p_ptp, |
| struct ptp_time *pts, |
| u32 events) |
| { |
| struct fec_ptp_data_t tmp_tx_time; |
| u16 seq_id; |
| |
| seq_id = SEQ_ID_OUT_OF_BAND; |
| |
| memset(&tmp_tx_time, 0, sizeof(struct fec_ptp_data_t)); |
| tmp_tx_time.key = ntohs(seq_id); |
| tmp_tx_time.ts_time.sec = pts->sec; |
| tmp_tx_time.ts_time.nsec = pts->nsec; |
| fec_ptp_insert(&(priv->txstamp), &tmp_tx_time, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| } |
| |
| /* out-of-band rx ts store */ |
| static void ptp_store_rxstamp(struct fec_ptp_private *priv, |
| struct ptp *p_ptp, |
| struct ptp_time *pts, |
| u32 events) |
| { |
| int control = PTP_MSG_ALL_OTHER; |
| u16 seq_id; |
| struct fec_ptp_data_t tmp_rx_time; |
| |
| /* out-of-band mode can't get seq_id */ |
| seq_id = SEQ_ID_OUT_OF_BAND; |
| |
| memset(&tmp_rx_time, 0, sizeof(struct fec_ptp_data_t)); |
| tmp_rx_time.key = ntohs(seq_id); |
| tmp_rx_time.ts_time.sec = pts->sec; |
| tmp_rx_time.ts_time.nsec = pts->nsec; |
| if (events & PTP_TS_RX_SYNC1) |
| control = PTP_MSG_SYNC; |
| else if (events & PTP_TS_RX_DELAY_REQ1) |
| control = PTP_MSG_DEL_REQ; |
| else if (events & PTP_TS_PDRQRE1) |
| control = PTP_MSG_P_DEL_REQ; |
| else if (events & PTP_TS_PDRSRE1) |
| control = PTP_MSG_DEL_RESP; |
| |
| switch (control) { |
| case PTP_MSG_SYNC: |
| fec_ptp_insert(&(priv->rx_time_sync), &tmp_rx_time, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| |
| case PTP_MSG_DEL_REQ: |
| fec_ptp_insert(&(priv->rx_time_del_req), &tmp_rx_time, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| |
| case PTP_MSG_P_DEL_REQ: |
| fec_ptp_insert(&(priv->rx_time_pdel_req), &tmp_rx_time, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| |
| case PTP_MSG_P_DEL_RESP: |
| fec_ptp_insert(&(priv->rx_time_pdel_resp), &tmp_rx_time, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| |
| default: |
| break; |
| } |
| |
| wake_up_interruptible(&ptp_rx_ts_wait); |
| |
| } |
| |
| /* in-band rx ts store */ |
| #ifdef CONFIG_IN_BAND |
| void fec_ptp_store_rxstamp(struct fec_ptp_private *priv, |
| struct sk_buff *skb, |
| struct bufdesc *bdp) |
| { |
| int msg_type, seq_id, control; |
| struct fec_ptp_data_t tmp_rx_time; |
| struct fec_ptp_private *fpp = priv; |
| u64 timestamp; |
| unsigned char *sp_id; |
| unsigned short portnum; |
| |
| /* Check for PTP Event */ |
| if ((bdp->cbd_sc & BD_ENET_RX_PTP) == 0) { |
| skb_pull(skb, 8); |
| return; |
| } |
| |
| /* Get ts from skb data */ |
| timestamp = *((u64 *)(skb->data)); |
| convert_rtc_time(×tamp, &(tmp_rx_time.ts_time)); |
| skb_pull(skb, 8); |
| |
| seq_id = *((u16 *)(skb->data + FEC_PTP_SEQ_ID_OFFS)); |
| control = *((u8 *)(skb->data + FEC_PTP_CTRL_OFFS)); |
| sp_id = skb->data + FEC_PTP_SPORT_ID_OFFS; |
| portnum = ntohs(*((unsigned short *)(sp_id + 8))); |
| |
| tmp_rx_time.key = ntohs(seq_id); |
| memcpy(tmp_rx_time.spid, sp_id, 8); |
| memcpy(tmp_rx_time.spid + 8, (unsigned char *)&portnum, 2); |
| |
| switch (control) { |
| |
| case PTP_MSG_SYNC: |
| fec_ptp_insert(&(priv->rx_time_sync), &tmp_rx_time, priv, |
| DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| |
| case PTP_MSG_DEL_REQ: |
| fec_ptp_insert(&(priv->rx_time_del_req), &tmp_rx_time, priv, |
| DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| |
| /* clear transportSpecific field*/ |
| case PTP_MSG_ALL_OTHER: |
| msg_type = (*((u8 *)(skb->data + |
| FEC_PTP_MSG_TYPE_OFFS))) & 0x0F; |
| switch (msg_type) { |
| case PTP_MSG_P_DEL_REQ: |
| fec_ptp_insert(&(priv->rx_time_pdel_req), &tmp_rx_time, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| case PTP_MSG_P_DEL_RESP: |
| fec_ptp_insert(&(priv->rx_time_pdel_resp), &tmp_rx_time, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| wake_up_interruptible(&ptp_rx_ts_wait); |
| } |
| #else |
| void fec_ptp_store_rxstamp(struct fec_ptp_private *priv, |
| struct sk_buff *skb, |
| struct bufdesc *bdp) |
| { |
| } |
| #endif |
| |
| /*PTP interrupt handler*/ |
| static irqreturn_t ptp_interrupt(int irq, void *dev_id) |
| { |
| struct ptp *p_ptp = (struct ptp *)dev_id; |
| void __iomem *mem_map = p_ptp->mem_map; |
| struct ptp_time ps; |
| u64 timestamp; |
| u32 events, orig_events; |
| |
| /*get valid events*/ |
| events = readl(mem_map + PTP_TMR_PEVENT); |
| while (events) { |
| if (events & PTP_TS_TX_FRAME1) { |
| /*read timestamp from register*/ |
| timestamp = ((u64)readl(mem_map + PTP_TMR_TXTS_H) |
| << 32) | |
| (readl(mem_map + PTP_TMR_TXTS_L)); |
| |
| /*clear event ASAP,hoping to prevent overrun*/ |
| writel((u32)PTP_TS_TX_FRAME1, |
| mem_map + PTP_TMR_PEVENT); |
| /*check for overrun(which incalidates last timestamp)*/ |
| events = readl(mem_map + PTP_TMR_PEVENT); |
| |
| if (events & PTP_TS_TX_OVR1) { |
| /*lost synchronization with TX timestamps*/ |
| /*clear overrun event*/ |
| writel(PTP_TS_TX_OVR1, |
| mem_map + PTP_TMR_PEVENT); |
| |
| p_ptp->tx_time_stamps_overrun++; |
| } else { |
| /*insert the Tx timestamps into the queue*/ |
| convert_rtc_time(×tamp, &ps); |
| ptp_store_txstamp(ptp_private, p_ptp, |
| &ps, orig_events); |
| |
| /*this event is never really masked, |
| *but it should be reported only |
| *if includeed in usre events mask*/ |
| if (p_ptp->events_mask & PTP_TS_TX_FRAME1) |
| p_ptp->tx_time_stamps++; |
| VDBG("tx interrupt\n"); |
| } |
| } |
| |
| /*typically only one of these events is relevant, |
| *depending on whether the device is PTP master or slave*/ |
| if (events & PTP_TS_RX_ALL) { |
| /*out-of-band mode:read timestamp |
| *from registers*/ |
| timestamp = ((u64)readl(mem_map + PTP_TMR_RXTS_H) |
| << 32) | |
| (readl(mem_map + PTP_TMR_RXTS_L)); |
| |
| /*clear event ASAP,hoping to prevent overrun*/ |
| orig_events = events; |
| writel((u32)(PTP_TS_RX_ALL), |
| mem_map + PTP_TMR_PEVENT); |
| |
| /*check for overrun (which invalidates |
| *last tiemstamp)*/ |
| events = readl(mem_map + PTP_TMR_PEVENT); |
| |
| if (events & PTP_TS_RX_OVR1) { |
| /*lost synchronization with Rx timestamp*/ |
| /*clear overrun event. clear the |
| *timestamp event as well, because |
| *it may have arrived after it was |
| *cleared above,but still it is not |
| *synchronized with received frames*/ |
| writel((u32)(PTP_TS_RX_ALL | |
| PTP_TS_RX_OVR1), |
| mem_map + PTP_TMR_RXTS_H); |
| p_ptp->rx_time_stamps_overrun++; |
| } else { |
| /*insert Rx timestamp into the queue*/ |
| convert_rtc_time(×tamp, &ps); |
| ptp_store_rxstamp(ptp_private, p_ptp, |
| &ps, orig_events); |
| |
| /*the Rx TS event is never masked in |
| *out-of-ban mode,but it should be |
| *reported only if included in user's |
| *event's mask*/ |
| if (p_ptp->events_mask & |
| (PTP_TS_RX_SYNC1 | |
| PTP_TS_RX_DELAY_REQ1)) |
| p_ptp->rx_time_stamps++; |
| } |
| } |
| |
| writel(~PTP_TMR_PEVENT_VALID, mem_map + PTP_TMR_PEVENT); |
| events = readl(mem_map + PTP_TMR_PEVENT); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void init_ptp_tsu(struct ptp *p_ptp) |
| { |
| struct ptp_driver_param *drv_param = p_ptp->driver_param; |
| void __iomem *mem_map; |
| u32 tsmr, pemask, events_mask; |
| |
| mem_map = p_ptp->mem_map; |
| |
| /*Tx timestamp events are required in all modes*/ |
| events_mask = PTP_TS_TX_FRAME1 | PTP_TS_TX_OVR1; |
| |
| /*read current values of TSU registers*/ |
| tsmr = readl(mem_map + PTP_TSMR); |
| pemask = readl(mem_map + PTP_TMR_PEMASK); |
| |
| if (drv_param->delivery_mode == e_PTP_TSU_DELIVERY_IN_BAND) { |
| tsmr |= PTP_TSMR_OPMODE1_IN_BAND; |
| events_mask &= ~(PTP_TS_TX_OVR1); |
| } else |
| /*rx timestamp events are required for out of band mode*/ |
| events_mask |= (PTP_TS_RX_SYNC1 | PTP_TS_RX_DELAY_REQ1 | |
| PTP_TS_RX_OVR1); |
| |
| pemask |= events_mask; |
| |
| /*update TSU register*/ |
| writel(tsmr, mem_map + PTP_TSMR); |
| writel(pemask, mem_map + PTP_TMR_PEMASK); |
| } |
| |
| /* ptp module init */ |
| static void ptp_tsu_init(struct ptp *p_ptp) |
| { |
| void __iomem *mem_map = p_ptp->mem_map; |
| |
| /*initialization of registered PTP modules*/ |
| init_ptp_parser(p_ptp); |
| |
| /*reset timestamp*/ |
| writel(0, mem_map + PTP_TSMR); |
| writel(0, mem_map + PTP_TMR_PEMASK); |
| writel(PTP_TMR_PEVENT_ALL, mem_map + PTP_TMR_PEVENT); |
| |
| } |
| |
| /* TSU configure function */ |
| static u32 ptp_tsu_enable(struct ptp *p_ptp) |
| { |
| void __iomem *mem_map; |
| u32 tsmr; |
| |
| /*enable the TSU*/ |
| mem_map = p_ptp->mem_map; |
| |
| /*set the TSU enable bit*/ |
| tsmr = readl(mem_map + PTP_TSMR); |
| tsmr |= PTP_TSMR_EN1; |
| |
| writel(tsmr, mem_map + PTP_TSMR); |
| |
| return 0; |
| } |
| |
| static u32 ptp_tsu_disable(struct ptp *p_ptp) |
| { |
| void __iomem *mem_map; |
| u32 tsmr; |
| |
| mem_map = p_ptp->mem_map; |
| |
| tsmr = readl(mem_map + PTP_TSMR); |
| tsmr &= ~(PTP_TSMR_EN1); |
| writel(tsmr, mem_map + PTP_TSMR); |
| |
| return 0; |
| } |
| |
| static int ptp_tsu_config_events_mask(struct ptp *p_ptp, |
| u32 events_mask) |
| { |
| |
| p_ptp->events_mask = events_mask; |
| |
| return 0; |
| } |
| |
| /* rtc configure function */ |
| static u32 rtc_enable(struct ptp_rtc *rtc, bool reset_clock) |
| { |
| u32 tmr_ctrl; |
| |
| tmr_ctrl = readl(rtc->mem_map + PTP_TMR_CTRL); |
| if (reset_clock) { |
| writel((tmr_ctrl | RTC_TMR_CTRL_TMSR), |
| rtc->mem_map + PTP_TMR_CTRL); |
| |
| /*clear TMR_OFF*/ |
| writel(0, rtc->mem_map + PTP_TMR_OFF_L); |
| writel(0, rtc->mem_map + PTP_TMR_OFF_H); |
| } |
| |
| writel((tmr_ctrl | RTC_TMR_CTRL_TE), |
| rtc->mem_map + PTP_TMR_CTRL); |
| |
| return 0; |
| } |
| |
| static u32 rtc_disable(struct ptp_rtc *rtc) |
| { |
| u32 tmr_ctrl; |
| |
| tmr_ctrl = readl(rtc->mem_map + PTP_TMR_CTRL); |
| writel((tmr_ctrl & ~RTC_TMR_CTRL_TE), |
| rtc->mem_map + PTP_TMR_CTRL); |
| |
| return 0; |
| } |
| |
| static u32 rtc_set_periodic_pulse( |
| struct ptp_rtc *rtc, |
| enum e_ptp_rtc_pulse_id pulse_ID, |
| u32 pulse_periodic) |
| { |
| u32 factor; |
| |
| if (rtc->start_pulse_on_alarm) { |
| /*from the spec:the ratio between the prescale register value |
| *and the fiper value should be decisable by the clock period |
| *FIPER_VALUE = (prescale_value * tclk_per * N) - tclk_per*/ |
| factor = (u32)((pulse_periodic + rtc->clock_period_nansec) / |
| (rtc->clock_period_nansec * rtc->output_clock_divisor)); |
| |
| if ((factor * rtc->clock_period_nansec * |
| rtc->output_clock_divisor) < |
| (pulse_periodic + rtc->clock_period_nansec)) |
| pulse_periodic = ((factor * rtc->clock_period_nansec * |
| rtc->output_clock_divisor) - |
| rtc->clock_period_nansec); |
| } |
| |
| /* Decrease it to fix PPS question (frequecy error)*/ |
| pulse_periodic -= rtc->clock_period_nansec; |
| |
| writel((u32)pulse_periodic, rtc->mem_map + PTP_TMR_FIPER1 + |
| (pulse_ID * 4)); |
| return 0; |
| } |
| |
| static u32 ptp_rtc_set_periodic_pulse( |
| struct ptp *p_ptp, |
| enum e_ptp_rtc_pulse_id pulse_ID, |
| struct ptp_time *ptime) |
| { |
| u32 ret; |
| u64 pulse_periodic; |
| |
| if (pulse_ID >= PTP_RTC_NUM_OF_PULSES) |
| return -1; |
| if (ptime->nsec < 0) |
| return -1; |
| |
| pulse_periodic = convert_unsigned_time(ptime); |
| if (pulse_periodic > 0xFFFFFFFF) |
| return -1; |
| |
| ret = rtc_set_periodic_pulse(p_ptp->rtc, pulse_ID, (u32)pulse_periodic); |
| |
| return ret; |
| } |
| |
| static u32 rtc_set_alarm( |
| struct ptp_rtc *rtc, |
| enum e_ptp_rtc_alarm_id alarm_ID, |
| u64 alarm_time) |
| { |
| u32 fiper; |
| int i; |
| |
| if ((alarm_ID == e_PTP_RTC_ALARM_1) && rtc->start_pulse_on_alarm) |
| alarm_time -= (3 * rtc->clock_period_nansec); |
| |
| /*TMR_ALARM_L must be written first*/ |
| writel((u32)alarm_time, rtc->mem_map + PTP_TMR_ALARM1_L + |
| (alarm_ID * 4)); |
| writel((u32)(alarm_time >> 32), |
| rtc->mem_map + PTP_TMR_ALARM1_H + (alarm_ID * 4)); |
| |
| if ((alarm_ID == e_PTP_RTC_ALARM_1) && rtc->start_pulse_on_alarm) { |
| /*we must write the TMR_FIPER register again(hardware |
| *constraint),From the spec:in order to keep tracking |
| *the prescale output clock each tiem before enabling |
| *the fiper,the user must reset the fiper by writing |
| *a new value to the reigster*/ |
| for (i = 0; i < PTP_RTC_NUM_OF_PULSES; i++) { |
| fiper = readl(rtc->mem_map + PTP_TMR_FIPER1 + |
| (i * 4)); |
| writel(fiper, rtc->mem_map + PTP_TMR_FIPER1 + |
| (i * 4)); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static u32 ptp_rtc_set_alarm( |
| struct ptp *p_ptp, |
| enum e_ptp_rtc_alarm_id alarm_ID, |
| struct ptp_time *ptime) |
| { |
| u32 ret; |
| u64 alarm_time; |
| |
| if (alarm_ID >= PTP_RTC_NUM_OF_ALARMS) |
| return -1; |
| if (ptime->nsec < 0) |
| return -1; |
| |
| alarm_time = convert_unsigned_time(ptime); |
| |
| ret = rtc_set_alarm(p_ptp->rtc, alarm_ID, alarm_time); |
| |
| return ret; |
| } |
| |
| /* rtc ioctl function */ |
| /*get the current time from RTC time counter register*/ |
| static void ptp_rtc_get_current_time(struct ptp *p_ptp, |
| struct ptp_time *p_time) |
| { |
| u64 times; |
| struct ptp_rtc *rtc = p_ptp->rtc; |
| |
| /*TMR_CNT_L must be read first to get an accurate value*/ |
| times = (u64)readl(rtc->mem_map + PTP_TMR_CNT_L); |
| times |= ((u64)readl(rtc->mem_map + PTP_TMR_CNT_H)) << 32; |
| |
| /*convert RTC time*/ |
| convert_rtc_time(×, p_time); |
| } |
| |
| static void ptp_rtc_reset_counter(struct ptp *p_ptp, struct ptp_time *p_time) |
| { |
| u64 times; |
| struct ptp_rtc *rtc = p_ptp->rtc; |
| |
| times = convert_unsigned_time(p_time); |
| writel((u32)times, rtc->mem_map + PTP_TMR_CNT_L); |
| writel((u32)(times >> 32), rtc->mem_map + PTP_TMR_CNT_H); |
| |
| } |
| |
| static void rtc_modify_frequency_compensation( |
| struct ptp_rtc *rtc, |
| u32 freq_compensation) |
| { |
| writel(freq_compensation, rtc->mem_map + PTP_TMR_ADD); |
| } |
| |
| /* Set the BD to ptp */ |
| int fec_ptp_do_txstamp(struct sk_buff *skb) |
| { |
| struct iphdr *iph; |
| struct udphdr *udph; |
| |
| if (skb->len > 44) { |
| /* Check if port is 319 for PTP Event, and check for UDP */ |
| iph = ip_hdr(skb); |
| if (iph == NULL || iph->protocol != FEC_PACKET_TYPE_UDP) |
| return 0; |
| |
| udph = udp_hdr(skb); |
| if (udph != NULL && ntohs(udph->dest) == 319) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int fec_get_tx_timestamp(struct fec_ptp_private *priv, |
| struct ptp_ts_data *pts, |
| struct ptp_time *tx_time) |
| { |
| struct fec_ptp_data_t tmp; |
| int flag; |
| |
| #ifdef CONFIG_IN_BAND |
| u8 mode; |
| tmp.key = pts->seq_id; |
| memcpy(tmp.spid, pts->spid, 10); |
| mode = pts->message_type; |
| |
| switch (mode) { |
| case PTP_MSG_SYNC: |
| flag = fec_ptp_find_and_remove(&(priv->tx_time_sync), &tmp, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| case PTP_MSG_DEL_REQ: |
| flag = fec_ptp_find_and_remove(&(priv->tx_time_del_req), &tmp, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| |
| case PTP_MSG_P_DEL_REQ: |
| flag = fec_ptp_find_and_remove(&(priv->tx_time_pdel_req), &tmp, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| case PTP_MSG_P_DEL_RESP: |
| flag = fec_ptp_find_and_remove(&(priv->tx_time_pdel_resp), |
| &tmp, priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| |
| default: |
| flag = 1; |
| printk(KERN_ERR "ERROR\n"); |
| break; |
| } |
| |
| if (!flag) { |
| tx_time->sec = tmp.ts_time.sec; |
| tx_time->nsec = tmp.ts_time.nsec; |
| return 0; |
| } else { |
| wait_event_interruptible_timeout(ptp_tx_ts_wait, 0, |
| PTP_GET_TX_TIMEOUT); |
| |
| switch (mode) { |
| case PTP_MSG_SYNC: |
| flag = fec_ptp_find_and_remove(&(priv->tx_time_sync), |
| &tmp, priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| case PTP_MSG_DEL_REQ: |
| flag = fec_ptp_find_and_remove( |
| &(priv->tx_time_del_req), &tmp, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| case PTP_MSG_P_DEL_REQ: |
| flag = fec_ptp_find_and_remove( |
| &(priv->tx_time_pdel_req), &tmp, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| case PTP_MSG_P_DEL_RESP: |
| flag = fec_ptp_find_and_remove( |
| &(priv->tx_time_pdel_resp), &tmp, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| break; |
| } |
| |
| if (flag == 0) { |
| tx_time->sec = tmp.ts_time.sec; |
| tx_time->nsec = tmp.ts_time.nsec; |
| return 0; |
| } |
| |
| return -1; |
| } |
| |
| #else |
| memset(tmp.spid, 0, 10); |
| tmp.key = SEQ_ID_OUT_OF_BAND; |
| |
| flag = fec_ptp_find_and_remove(&(priv->txstamp), &tmp, |
| priv, DEFAULT_PTP_TX_BUF_SZ); |
| tx_time->sec = tmp.ts_time.sec; |
| tx_time->nsec = tmp.ts_time.nsec; |
| return 0; |
| #endif |
| } |
| |
| static uint8_t fec_get_rx_timestamp(struct fec_ptp_private *priv, |
| struct ptp_ts_data *pts, |
| struct ptp_time *rx_time) |
| { |
| struct fec_ptp_data_t tmp; |
| int flag; |
| u8 mode; |
| |
| #ifdef CONFIG_IN_BAND |
| tmp.key = pts->seq_id; |
| memcpy(tmp.spid, pts->spid, 10); |
| #else |
| memset(tmp.spid, 0, 10); |
| tmp.key = SEQ_ID_OUT_OF_BAND; |
| #endif |
| mode = pts->message_type; |
| switch (mode) { |
| case PTP_MSG_SYNC: |
| flag = fec_ptp_find_and_remove(&(priv->rx_time_sync), &tmp, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| case PTP_MSG_DEL_REQ: |
| flag = fec_ptp_find_and_remove(&(priv->rx_time_del_req), &tmp, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| |
| case PTP_MSG_P_DEL_REQ: |
| flag = fec_ptp_find_and_remove(&(priv->rx_time_pdel_req), &tmp, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| case PTP_MSG_P_DEL_RESP: |
| flag = fec_ptp_find_and_remove(&(priv->rx_time_pdel_resp), |
| &tmp, priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| |
| default: |
| flag = 1; |
| printk(KERN_ERR "ERROR\n"); |
| break; |
| } |
| |
| if (!flag) { |
| rx_time->sec = tmp.ts_time.sec; |
| rx_time->nsec = tmp.ts_time.nsec; |
| return 0; |
| } else { |
| wait_event_interruptible_timeout(ptp_rx_ts_wait, 0, |
| PTP_GET_RX_TIMEOUT); |
| |
| switch (mode) { |
| case PTP_MSG_SYNC: |
| flag = fec_ptp_find_and_remove(&(priv->rx_time_sync), |
| &tmp, priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| case PTP_MSG_DEL_REQ: |
| flag = fec_ptp_find_and_remove( |
| &(priv->rx_time_del_req), &tmp, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| case PTP_MSG_P_DEL_REQ: |
| flag = fec_ptp_find_and_remove( |
| &(priv->rx_time_pdel_req), &tmp, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| case PTP_MSG_P_DEL_RESP: |
| flag = fec_ptp_find_and_remove( |
| &(priv->rx_time_pdel_resp), &tmp, |
| priv, DEFAULT_PTP_RX_BUF_SZ); |
| break; |
| } |
| |
| if (flag == 0) { |
| rx_time->sec = tmp.ts_time.sec; |
| rx_time->nsec = tmp.ts_time.nsec; |
| return 0; |
| } |
| |
| return -1; |
| } |
| } |
| |
| /* 1588 Module start */ |
| int fec_ptp_start(struct fec_ptp_private *priv) |
| { |
| struct ptp *p_ptp = ptp_dev; |
| |
| /* Enable TSU clk */ |
| clk_enable(p_ptp->clk); |
| |
| /*initialize the TSU using the register function*/ |
| init_ptp_tsu(p_ptp); |
| |
| /* start counter */ |
| p_ptp->fpp = ptp_private; |
| ptp_tsu_enable(p_ptp); |
| |
| return 0; |
| } |
| |
| /* Cleanup routine for 1588 module. |
| * When PTP is disabled this routing is called */ |
| void fec_ptp_stop(struct fec_ptp_private *priv) |
| { |
| struct ptp *p_ptp = ptp_dev; |
| |
| /* stop counter */ |
| ptp_tsu_disable(p_ptp); |
| clk_disable(p_ptp->clk); |
| |
| return; |
| } |
| |
| static int ptp_open(struct inode *inode, struct file *file) |
| { |
| return 0; |
| } |
| |
| static int ptp_release(struct inode *inode, struct file *file) |
| { |
| return 0; |
| } |
| |
| /* ptp device ioctl function */ |
| static int ptp_ioctl( |
| struct inode *inode, |
| struct file *file, |
| unsigned int cmd, |
| unsigned long arg) |
| { |
| struct ptp_rtc_time *cnt; |
| struct ptp_rtc_time curr_time; |
| struct ptp_time rx_time, tx_time; |
| struct ptp_ts_data *p_ts; |
| struct ptp_set_comp *p_comp; |
| struct fec_ptp_private *priv; |
| int retval = 0; |
| |
| priv = (struct fec_ptp_private *) ptp_private; |
| switch (cmd) { |
| case PTP_GET_RX_TIMESTAMP: |
| p_ts = (struct ptp_ts_data *)arg; |
| retval = fec_get_rx_timestamp(priv, p_ts, &rx_time); |
| if (retval == 0) |
| retval = copy_to_user((void __user *)(&(p_ts->ts)), |
| &rx_time, sizeof(rx_time)); |
| break; |
| case PTP_GET_TX_TIMESTAMP: |
| p_ts = (struct ptp_ts_data *)arg; |
| fec_get_tx_timestamp(priv, p_ts, &tx_time); |
| retval = copy_to_user((void __user *)(&(p_ts->ts)), |
| &tx_time, sizeof(tx_time)); |
| break; |
| case PTP_GET_CURRENT_TIME: |
| ptp_rtc_get_current_time(ptp_dev, &(curr_time.rtc_time)); |
| retval = copy_to_user((void __user *)arg, &curr_time, |
| sizeof(curr_time)); |
| break; |
| case PTP_SET_RTC_TIME: |
| cnt = (struct ptp_rtc_time *)arg; |
| ptp_rtc_reset_counter(ptp_dev, &(cnt->rtc_time)); |
| break; |
| case PTP_FLUSH_TIMESTAMP: |
| /* reset sync buffer */ |
| priv->rx_time_sync.head = 0; |
| priv->rx_time_sync.tail = 0; |
| /* reset delay_req buffer */ |
| priv->rx_time_del_req.head = 0; |
| priv->rx_time_del_req.tail = 0; |
| /* reset pdelay_req buffer */ |
| priv->rx_time_pdel_req.head = 0; |
| priv->rx_time_pdel_req.tail = 0; |
| /* reset pdelay_resp buffer */ |
| priv->rx_time_pdel_resp.head = 0; |
| priv->rx_time_pdel_resp.tail = 0; |
| /* reset sync buffer */ |
| priv->tx_time_sync.head = 0; |
| priv->tx_time_sync.tail = 0; |
| /* reset delay_req buffer */ |
| priv->tx_time_del_req.head = 0; |
| priv->tx_time_del_req.tail = 0; |
| /* reset pdelay_req buffer */ |
| priv->tx_time_pdel_req.head = 0; |
| priv->tx_time_pdel_req.tail = 0; |
| /* reset pdelay_resp buffer */ |
| priv->tx_time_pdel_resp.head = 0; |
| priv->tx_time_pdel_resp.tail = 0; |
| priv->txstamp.head = 0; |
| priv->txstamp.tail = 0; |
| break; |
| case PTP_SET_COMPENSATION: |
| p_comp = (struct ptp_set_comp *)arg; |
| rtc_modify_frequency_compensation(ptp_dev->rtc, |
| p_comp->freq_compensation); |
| break; |
| case PTP_GET_ORIG_COMP: |
| ((struct ptp_get_comp *)arg)->dw_origcomp = |
| ptp_dev->orig_freq_comp; |
| break; |
| default: |
| return -EINVAL; |
| } |
| return retval; |
| } |
| |
| static const struct file_operations ptp_fops = { |
| .owner = THIS_MODULE, |
| .llseek = NULL, |
| .read = NULL, |
| .write = NULL, |
| .ioctl = ptp_ioctl, |
| .open = ptp_open, |
| .release = ptp_release, |
| }; |
| |
| static int init_ptp_driver(struct ptp *p_ptp) |
| { |
| struct ptp_time ptime; |
| int ret; |
| |
| /* configure RTC param */ |
| ret = ptp_rtc_config(p_ptp->rtc); |
| if (ret) |
| return -1; |
| |
| /* initialize RTC register */ |
| ptp_rtc_init(p_ptp); |
| |
| /* initialize PTP TSU param */ |
| ptp_param_config(p_ptp); |
| |
| /* set TSU configuration parameters */ |
| #ifdef CONFIG_IN_BAND |
| p_ptp->driver_param->delivery_mode = e_PTP_TSU_DELIVERY_IN_BAND; |
| #else |
| p_ptp->driver_param->delivery_mode = e_PTP_TSU_DELIVERY_OUT_OF_BAND; |
| #endif |
| |
| if (ptp_tsu_config_events_mask(p_ptp, DEFAULT_events_PTP_Mask)) |
| goto end; |
| |
| /* initialize PTP TSU register */ |
| ptp_tsu_init(p_ptp); |
| |
| /* set periodic pulses */ |
| ptime.sec = USE_CASE_PULSE_1_PERIOD / NANOSEC_IN_SEC; |
| ptime.nsec = USE_CASE_PULSE_1_PERIOD % NANOSEC_IN_SEC; |
| ret = ptp_rtc_set_periodic_pulse(p_ptp, e_PTP_RTC_PULSE_1, &ptime); |
| if (ret) |
| goto end; |
| |
| ptime.sec = USE_CASE_PULSE_2_PERIOD / NANOSEC_IN_SEC; |
| ptime.nsec = USE_CASE_PULSE_2_PERIOD % NANOSEC_IN_SEC; |
| ret = ptp_rtc_set_periodic_pulse(p_ptp, e_PTP_RTC_PULSE_2, &ptime); |
| if (ret) |
| goto end; |
| |
| ptime.sec = USE_CASE_PULSE_3_PERIOD / NANOSEC_IN_SEC; |
| ptime.nsec = USE_CASE_PULSE_3_PERIOD % NANOSEC_IN_SEC; |
| ret = ptp_rtc_set_periodic_pulse(p_ptp, e_PTP_RTC_PULSE_3, &ptime); |
| if (ret) |
| goto end; |
| |
| /* set alarm */ |
| ptime.sec = (USE_CASE_ALARM_1_TIME / NANOSEC_IN_SEC); |
| ptime.nsec = (USE_CASE_ALARM_1_TIME % NANOSEC_IN_SEC); |
| ret = ptp_rtc_set_alarm(p_ptp, e_PTP_RTC_ALARM_1, &ptime); |
| if (ret) |
| goto end; |
| |
| ptime.sec = (USE_CASE_ALARM_2_TIME / NANOSEC_IN_SEC); |
| ptime.nsec = (USE_CASE_ALARM_2_TIME % NANOSEC_IN_SEC); |
| ret = ptp_rtc_set_alarm(p_ptp, e_PTP_RTC_ALARM_2, &ptime); |
| if (ret) |
| goto end; |
| |
| /* enable the RTC */ |
| ret = rtc_enable(p_ptp->rtc, FALSE); |
| if (ret) |
| goto end; |
| |
| udelay(10); |
| ptp_rtc_get_current_time(p_ptp, &ptime); |
| if (ptime.nsec == 0) { |
| printk(KERN_ERR "PTP RTC is not running\n"); |
| goto end; |
| } |
| |
| if (register_chrdev(PTP_MAJOR, "ptp", &ptp_fops)) |
| printk(KERN_ERR "Unable to register PTP device as char\n"); |
| else |
| printk(KERN_INFO "Register PTP as char device\n"); |
| |
| end: |
| return ret; |
| } |
| |
| static void ptp_free(void) |
| { |
| rtc_disable(ptp_dev->rtc); |
| /*unregister the PTP device*/ |
| unregister_chrdev(PTP_MAJOR, "ptp"); |
| } |
| |
| /* |
| * Resource required for accessing 1588 Timer Registers. |
| */ |
| int fec_ptp_init(struct fec_ptp_private *priv, int id) |
| { |
| fec_ptp_init_circ(&(priv->rx_time_sync), DEFAULT_PTP_RX_BUF_SZ); |
| fec_ptp_init_circ(&(priv->rx_time_del_req), DEFAULT_PTP_RX_BUF_SZ); |
| fec_ptp_init_circ(&(priv->rx_time_pdel_req), DEFAULT_PTP_RX_BUF_SZ); |
| fec_ptp_init_circ(&(priv->rx_time_pdel_resp), DEFAULT_PTP_RX_BUF_SZ); |
| fec_ptp_init_circ(&(priv->tx_time_sync), DEFAULT_PTP_TX_BUF_SZ); |
| fec_ptp_init_circ(&(priv->tx_time_del_req), DEFAULT_PTP_TX_BUF_SZ); |
| fec_ptp_init_circ(&(priv->tx_time_pdel_req), DEFAULT_PTP_TX_BUF_SZ); |
| fec_ptp_init_circ(&(priv->tx_time_pdel_resp), DEFAULT_PTP_TX_BUF_SZ); |
| |
| fec_ptp_init_circ(&(priv->txstamp), DEFAULT_PTP_TX_BUF_SZ); |
| |
| spin_lock_init(&priv->ptp_lock); |
| spin_lock_init(&priv->cnt_lock); |
| ptp_private = priv; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(fec_ptp_init); |
| |
| void fec_ptp_cleanup(struct fec_ptp_private *priv) |
| { |
| if (priv->rx_time_sync.buf) |
| vfree(priv->rx_time_sync.buf); |
| if (priv->rx_time_del_req.buf) |
| vfree(priv->rx_time_del_req.buf); |
| if (priv->rx_time_pdel_req.buf) |
| vfree(priv->rx_time_pdel_req.buf); |
| if (priv->rx_time_pdel_resp.buf) |
| vfree(priv->rx_time_pdel_resp.buf); |
| if (priv->tx_time_sync.buf) |
| vfree(priv->tx_time_sync.buf); |
| if (priv->tx_time_del_req.buf) |
| vfree(priv->tx_time_del_req.buf); |
| if (priv->tx_time_pdel_req.buf) |
| vfree(priv->tx_time_pdel_req.buf); |
| if (priv->tx_time_pdel_resp.buf) |
| vfree(priv->tx_time_pdel_resp.buf); |
| if (priv->txstamp.buf) |
| vfree(priv->txstamp.buf); |
| |
| ptp_free(); |
| } |
| EXPORT_SYMBOL(fec_ptp_cleanup); |
| |
| /* probe just register memory and irq */ |
| static int __devinit |
| ptp_probe(struct platform_device *pdev) |
| { |
| int i, irq, ret = 0; |
| struct resource *r; |
| |
| /* setup board info structure */ |
| ptp_dev = kzalloc(sizeof(struct ptp), GFP_KERNEL); |
| if (!ptp_dev) { |
| ret = -ENOMEM; |
| goto err1; |
| } |
| ptp_dev->rtc = kzalloc(sizeof(struct ptp_rtc), |
| GFP_KERNEL); |
| if (!ptp_dev->rtc) { |
| ret = -ENOMEM; |
| goto err2; |
| } |
| |
| /* PTP register memory */ |
| r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!r) { |
| ret = -ENXIO; |
| goto err3; |
| } |
| |
| r = request_mem_region(r->start, resource_size(r), pdev->name); |
| if (!r) { |
| ret = -EBUSY; |
| goto err3; |
| } |
| |
| ptp_dev->mem_map = ioremap(r->start, resource_size(r)); |
| if (!ptp_dev->mem_map) { |
| ret = -ENOMEM; |
| goto failed_ioremap; |
| } |
| |
| /* RTC register memory */ |
| r = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (!r) { |
| ret = -ENXIO; |
| goto err4; |
| } |
| |
| r = request_mem_region(r->start, resource_size(r), "PTP_RTC"); |
| if (!r) { |
| ret = -EBUSY; |
| goto err4; |
| } |
| |
| ptp_dev->rtc->mem_map = ioremap(r->start, resource_size(r)); |
| |
| if (!ptp_dev->rtc->mem_map) { |
| ret = -ENOMEM; |
| goto failed_ioremap1; |
| } |
| |
| /* This device has up to two irqs on some platforms */ |
| for (i = 0; i < 2; i++) { |
| irq = platform_get_irq(pdev, i); |
| if (i && irq < 0) |
| break; |
| if (i == 0) |
| ret = request_irq(irq, ptp_interrupt, |
| IRQF_DISABLED, pdev->name, ptp_dev); |
| else |
| ret = request_irq(irq, ptp_rtc_interrupt, |
| IRQF_DISABLED, "ptp_rtc", ptp_dev); |
| if (ret) { |
| while (i >= 0) { |
| irq = platform_get_irq(pdev, i); |
| free_irq(irq, ptp_dev); |
| i--; |
| } |
| goto failed_irq; |
| } |
| } |
| |
| ptp_dev->rtc->clk = clk_get(NULL, "ieee_rtc_clk"); |
| if (IS_ERR(ptp_dev->rtc->clk)) { |
| ret = PTR_ERR(ptp_dev->rtc->clk); |
| goto failed_clk1; |
| } |
| |
| ptp_dev->clk = clk_get(&pdev->dev, "ieee_1588_clk"); |
| if (IS_ERR(ptp_dev->clk)) { |
| ret = PTR_ERR(ptp_dev->clk); |
| goto failed_clk2; |
| } |
| |
| clk_enable(ptp_dev->clk); |
| |
| init_ptp_driver(ptp_dev); |
| clk_disable(ptp_dev->clk); |
| |
| return 0; |
| |
| failed_clk2: |
| clk_put(ptp_dev->rtc->clk); |
| failed_clk1: |
| for (i = 0; i < 2; i++) { |
| irq = platform_get_irq(pdev, i); |
| if (irq > 0) |
| free_irq(irq, ptp_dev); |
| } |
| failed_irq: |
| iounmap((void __iomem *)ptp_dev->rtc->mem_map); |
| failed_ioremap1: |
| err4: |
| iounmap((void __iomem *)ptp_dev->mem_map); |
| failed_ioremap: |
| err3: |
| kfree(ptp_dev->rtc); |
| err2: |
| kfree(ptp_dev); |
| err1: |
| return ret; |
| } |
| |
| static int __devexit |
| ptp_drv_remove(struct platform_device *pdev) |
| { |
| clk_disable(ptp_dev->clk); |
| clk_put(ptp_dev->clk); |
| clk_put(ptp_dev->rtc->clk); |
| iounmap((void __iomem *)ptp_dev->rtc->mem_map); |
| iounmap((void __iomem *)ptp_dev->mem_map); |
| kfree(ptp_dev->rtc->driver_param); |
| kfree(ptp_dev->rtc); |
| kfree(ptp_dev->driver_param); |
| kfree(ptp_dev); |
| return 0; |
| } |
| |
| static struct platform_driver ptp_driver = { |
| .driver = { |
| .name = "ptp", |
| .owner = THIS_MODULE, |
| }, |
| .probe = ptp_probe, |
| .remove = __devexit_p(ptp_drv_remove), |
| }; |
| |
| static int __init |
| ptp_module_init(void) |
| { |
| printk(KERN_INFO "iMX PTP Driver\n"); |
| |
| return platform_driver_register(&ptp_driver); |
| } |
| |
| static void __exit |
| ptp_cleanup(void) |
| { |
| platform_driver_unregister(&ptp_driver); |
| } |
| |
| module_exit(ptp_cleanup); |
| module_init(ptp_module_init); |
| |
| MODULE_LICENSE("GPL"); |