forked from mirrors/linux
		
	can: gs_usb: add RX and TX hardware timestamp support
Add support for hardware timestamps, if the firmware includes it as a feature via the GS_CAN_FEATURE_HW_TIMESTAMP flag. Check for this feature during probe, extend the RX expected length if it is and enable it during open. The struct classic_can_ts and struct canfd_ts are extended to include the µs timestamp following data as defined in the firmware. The timestamp is then captured and set using skb_hwtstamps() on each RX and TX. The frame µs timestamp is provided from a 32 bit 1 MHz timer which rolls over every 4294 seconds, so a cyclecounter, timecounter, and delayed worker are used to convert the timer into a proper ns timestamp - same implementation as commitefd8d98dfb("can: mcp251xfd: add HW timestamp infrastructure"). Hardware timestamps are added to capabilities as commitb1f6b93e67("can: mcp251xfd: advertise timestamping capabilities and add ioctl support"). Signed-off-by: John Whittington <git@jbrengineering.co.uk> Link: https://github.com/candle-usb/candleLight_fw/issues/100 Link: https://lore.kernel.org/all/20220827221548.3291393-3-mkl@pengutronix.de Co-developed-by: Marc Kleine-Budde <mkl@pengutronix.de> Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
		
							parent
							
								
									49c007b9ec
								
							
						
					
					
						commit
						45dfa45f52
					
				
					 1 changed files with 186 additions and 7 deletions
				
			
		|  | @ -10,12 +10,16 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include <linux/bitfield.h> | #include <linux/bitfield.h> | ||||||
|  | #include <linux/clocksource.h> | ||||||
| #include <linux/ethtool.h> | #include <linux/ethtool.h> | ||||||
| #include <linux/init.h> | #include <linux/init.h> | ||||||
| #include <linux/module.h> | #include <linux/module.h> | ||||||
| #include <linux/netdevice.h> | #include <linux/netdevice.h> | ||||||
| #include <linux/signal.h> | #include <linux/signal.h> | ||||||
|  | #include <linux/timecounter.h> | ||||||
|  | #include <linux/units.h> | ||||||
| #include <linux/usb.h> | #include <linux/usb.h> | ||||||
|  | #include <linux/workqueue.h> | ||||||
| 
 | 
 | ||||||
| #include <linux/can.h> | #include <linux/can.h> | ||||||
| #include <linux/can/dev.h> | #include <linux/can/dev.h> | ||||||
|  | @ -37,6 +41,14 @@ | ||||||
| #define GS_USB_ENDPOINT_IN 1 | #define GS_USB_ENDPOINT_IN 1 | ||||||
| #define GS_USB_ENDPOINT_OUT 2 | #define GS_USB_ENDPOINT_OUT 2 | ||||||
| 
 | 
 | ||||||
|  | /* Timestamp 32 bit timer runs at 1 MHz (1 µs tick). Worker accounts
 | ||||||
|  |  * for timer overflow (will be after ~71 minutes) | ||||||
|  |  */ | ||||||
|  | #define GS_USB_TIMESTAMP_TIMER_HZ (1 * HZ_PER_MHZ) | ||||||
|  | #define GS_USB_TIMESTAMP_WORK_DELAY_SEC 1800 | ||||||
|  | static_assert(GS_USB_TIMESTAMP_WORK_DELAY_SEC < | ||||||
|  | 	      CYCLECOUNTER_MASK(32) / GS_USB_TIMESTAMP_TIMER_HZ / 2); | ||||||
|  | 
 | ||||||
| /* Device specific constants */ | /* Device specific constants */ | ||||||
| enum gs_usb_breq { | enum gs_usb_breq { | ||||||
| 	GS_USB_BREQ_HOST_FORMAT = 0, | 	GS_USB_BREQ_HOST_FORMAT = 0, | ||||||
|  | @ -199,6 +211,11 @@ struct classic_can { | ||||||
| 	u8 data[8]; | 	u8 data[8]; | ||||||
| } __packed; | } __packed; | ||||||
| 
 | 
 | ||||||
|  | struct classic_can_ts { | ||||||
|  | 	u8 data[8]; | ||||||
|  | 	__le32 timestamp_us; | ||||||
|  | } __packed; | ||||||
|  | 
 | ||||||
| struct classic_can_quirk { | struct classic_can_quirk { | ||||||
| 	u8 data[8]; | 	u8 data[8]; | ||||||
| 	u8 quirk; | 	u8 quirk; | ||||||
|  | @ -208,6 +225,11 @@ struct canfd { | ||||||
| 	u8 data[64]; | 	u8 data[64]; | ||||||
| } __packed; | } __packed; | ||||||
| 
 | 
 | ||||||
|  | struct canfd_ts { | ||||||
|  | 	u8 data[64]; | ||||||
|  | 	__le32 timestamp_us; | ||||||
|  | } __packed; | ||||||
|  | 
 | ||||||
| struct canfd_quirk { | struct canfd_quirk { | ||||||
| 	u8 data[64]; | 	u8 data[64]; | ||||||
| 	u8 quirk; | 	u8 quirk; | ||||||
|  | @ -224,8 +246,10 @@ struct gs_host_frame { | ||||||
| 
 | 
 | ||||||
| 	union { | 	union { | ||||||
| 		DECLARE_FLEX_ARRAY(struct classic_can, classic_can); | 		DECLARE_FLEX_ARRAY(struct classic_can, classic_can); | ||||||
|  | 		DECLARE_FLEX_ARRAY(struct classic_can_ts, classic_can_ts); | ||||||
| 		DECLARE_FLEX_ARRAY(struct classic_can_quirk, classic_can_quirk); | 		DECLARE_FLEX_ARRAY(struct classic_can_quirk, classic_can_quirk); | ||||||
| 		DECLARE_FLEX_ARRAY(struct canfd, canfd); | 		DECLARE_FLEX_ARRAY(struct canfd, canfd); | ||||||
|  | 		DECLARE_FLEX_ARRAY(struct canfd_ts, canfd_ts); | ||||||
| 		DECLARE_FLEX_ARRAY(struct canfd_quirk, canfd_quirk); | 		DECLARE_FLEX_ARRAY(struct canfd_quirk, canfd_quirk); | ||||||
| 	}; | 	}; | ||||||
| } __packed; | } __packed; | ||||||
|  | @ -259,6 +283,11 @@ struct gs_can { | ||||||
| 	struct can_bittiming_const bt_const, data_bt_const; | 	struct can_bittiming_const bt_const, data_bt_const; | ||||||
| 	unsigned int channel;	/* channel number */ | 	unsigned int channel;	/* channel number */ | ||||||
| 
 | 
 | ||||||
|  | 	/* time counter for hardware timestamps */ | ||||||
|  | 	struct cyclecounter cc; | ||||||
|  | 	struct timecounter tc; | ||||||
|  | 	struct delayed_work timestamp; | ||||||
|  | 
 | ||||||
| 	u32 feature; | 	u32 feature; | ||||||
| 	unsigned int hf_size_tx; | 	unsigned int hf_size_tx; | ||||||
| 
 | 
 | ||||||
|  | @ -351,6 +380,87 @@ static int gs_cmd_reset(struct gs_can *gsdev) | ||||||
| 	return rc; | 	return rc; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static inline int gs_usb_get_timestamp(const struct gs_can *dev, | ||||||
|  | 				       u32 *timestamp_p) | ||||||
|  | { | ||||||
|  | 	__le32 timestamp; | ||||||
|  | 	int rc; | ||||||
|  | 
 | ||||||
|  | 	rc = usb_control_msg_recv(interface_to_usbdev(dev->iface), | ||||||
|  | 				  usb_sndctrlpipe(interface_to_usbdev(dev->iface), 0), | ||||||
|  | 				  GS_USB_BREQ_TIMESTAMP, | ||||||
|  | 				  USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, | ||||||
|  | 				  dev->channel, 0, | ||||||
|  | 				  ×tamp, sizeof(timestamp), | ||||||
|  | 				  USB_CTRL_GET_TIMEOUT, | ||||||
|  | 				  GFP_KERNEL); | ||||||
|  | 	if (rc) | ||||||
|  | 		return rc; | ||||||
|  | 
 | ||||||
|  | 	*timestamp_p = le32_to_cpu(timestamp); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static u64 gs_usb_timestamp_read(const struct cyclecounter *cc) | ||||||
|  | { | ||||||
|  | 	const struct gs_can *dev; | ||||||
|  | 	u32 timestamp = 0; | ||||||
|  | 	int err; | ||||||
|  | 
 | ||||||
|  | 	dev = container_of(cc, struct gs_can, cc); | ||||||
|  | 	err = gs_usb_get_timestamp(dev, ×tamp); | ||||||
|  | 	if (err) | ||||||
|  | 		netdev_err(dev->netdev, | ||||||
|  | 			   "Error %d while reading timestamp. HW timestamps may be inaccurate.", | ||||||
|  | 			   err); | ||||||
|  | 
 | ||||||
|  | 	return timestamp; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void gs_usb_timestamp_work(struct work_struct *work) | ||||||
|  | { | ||||||
|  | 	struct delayed_work *delayed_work = to_delayed_work(work); | ||||||
|  | 	struct gs_can *dev; | ||||||
|  | 
 | ||||||
|  | 	dev = container_of(delayed_work, struct gs_can, timestamp); | ||||||
|  | 	timecounter_read(&dev->tc); | ||||||
|  | 
 | ||||||
|  | 	schedule_delayed_work(&dev->timestamp, | ||||||
|  | 			      GS_USB_TIMESTAMP_WORK_DELAY_SEC * HZ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void gs_usb_skb_set_timestamp(const struct gs_can *dev, | ||||||
|  | 				     struct sk_buff *skb, u32 timestamp) | ||||||
|  | { | ||||||
|  | 	struct skb_shared_hwtstamps *hwtstamps = skb_hwtstamps(skb); | ||||||
|  | 	u64 ns; | ||||||
|  | 
 | ||||||
|  | 	ns = timecounter_cyc2time(&dev->tc, timestamp); | ||||||
|  | 	hwtstamps->hwtstamp = ns_to_ktime(ns); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void gs_usb_timestamp_init(struct gs_can *dev) | ||||||
|  | { | ||||||
|  | 	struct cyclecounter *cc = &dev->cc; | ||||||
|  | 
 | ||||||
|  | 	cc->read = gs_usb_timestamp_read; | ||||||
|  | 	cc->mask = CYCLECOUNTER_MASK(32); | ||||||
|  | 	cc->shift = 32 - bits_per(NSEC_PER_SEC / GS_USB_TIMESTAMP_TIMER_HZ); | ||||||
|  | 	cc->mult = clocksource_hz2mult(GS_USB_TIMESTAMP_TIMER_HZ, cc->shift); | ||||||
|  | 
 | ||||||
|  | 	timecounter_init(&dev->tc, &dev->cc, ktime_get_real_ns()); | ||||||
|  | 
 | ||||||
|  | 	INIT_DELAYED_WORK(&dev->timestamp, gs_usb_timestamp_work); | ||||||
|  | 	schedule_delayed_work(&dev->timestamp, | ||||||
|  | 			      GS_USB_TIMESTAMP_WORK_DELAY_SEC * HZ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void gs_usb_timestamp_stop(struct gs_can *dev) | ||||||
|  | { | ||||||
|  | 	cancel_delayed_work_sync(&dev->timestamp); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void gs_update_state(struct gs_can *dev, struct can_frame *cf) | static void gs_update_state(struct gs_can *dev, struct can_frame *cf) | ||||||
| { | { | ||||||
| 	struct can_device_stats *can_stats = &dev->can.can_stats; | 	struct can_device_stats *can_stats = &dev->can.can_stats; | ||||||
|  | @ -376,6 +486,24 @@ static void gs_update_state(struct gs_can *dev, struct can_frame *cf) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void gs_usb_set_timestamp(const struct gs_can *dev, struct sk_buff *skb, | ||||||
|  | 				 const struct gs_host_frame *hf) | ||||||
|  | { | ||||||
|  | 	u32 timestamp; | ||||||
|  | 
 | ||||||
|  | 	if (!(dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP)) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	if (hf->flags & GS_CAN_FLAG_FD) | ||||||
|  | 		timestamp = le32_to_cpu(hf->canfd_ts->timestamp_us); | ||||||
|  | 	else | ||||||
|  | 		timestamp = le32_to_cpu(hf->classic_can_ts->timestamp_us); | ||||||
|  | 
 | ||||||
|  | 	gs_usb_skb_set_timestamp(dev, skb, timestamp); | ||||||
|  | 
 | ||||||
|  | 	return; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void gs_usb_receive_bulk_callback(struct urb *urb) | static void gs_usb_receive_bulk_callback(struct urb *urb) | ||||||
| { | { | ||||||
| 	struct gs_usb *usbcan = urb->context; | 	struct gs_usb *usbcan = urb->context; | ||||||
|  | @ -443,6 +571,8 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) | ||||||
| 				gs_update_state(dev, cf); | 				gs_update_state(dev, cf); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		gs_usb_set_timestamp(dev, skb, hf); | ||||||
|  | 
 | ||||||
| 		netdev->stats.rx_packets++; | 		netdev->stats.rx_packets++; | ||||||
| 		netdev->stats.rx_bytes += hf->can_dlc; | 		netdev->stats.rx_bytes += hf->can_dlc; | ||||||
| 
 | 
 | ||||||
|  | @ -465,6 +595,9 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) | ||||||
| 			goto resubmit_urb; | 			goto resubmit_urb; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		skb = dev->can.echo_skb[hf->echo_id]; | ||||||
|  | 		gs_usb_set_timestamp(dev, skb, hf); | ||||||
|  | 
 | ||||||
| 		netdev->stats.tx_packets++; | 		netdev->stats.tx_packets++; | ||||||
| 		netdev->stats.tx_bytes += can_get_echo_skb(netdev, hf->echo_id, | 		netdev->stats.tx_bytes += can_get_echo_skb(netdev, hf->echo_id, | ||||||
| 							   NULL); | 							   NULL); | ||||||
|  | @ -823,6 +956,10 @@ static int gs_can_open(struct net_device *netdev) | ||||||
| 	if (ctrlmode & CAN_CTRLMODE_3_SAMPLES) | 	if (ctrlmode & CAN_CTRLMODE_3_SAMPLES) | ||||||
| 		flags |= GS_CAN_MODE_TRIPLE_SAMPLE; | 		flags |= GS_CAN_MODE_TRIPLE_SAMPLE; | ||||||
| 
 | 
 | ||||||
|  | 	/* if hardware supports timestamps, enable it */ | ||||||
|  | 	if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) | ||||||
|  | 		flags |= GS_CAN_MODE_HW_TIMESTAMP; | ||||||
|  | 
 | ||||||
| 	/* finally start device */ | 	/* finally start device */ | ||||||
| 	dm->mode = cpu_to_le32(GS_CAN_MODE_START); | 	dm->mode = cpu_to_le32(GS_CAN_MODE_START); | ||||||
| 	dm->flags = cpu_to_le32(flags); | 	dm->flags = cpu_to_le32(flags); | ||||||
|  | @ -840,6 +977,10 @@ static int gs_can_open(struct net_device *netdev) | ||||||
| 
 | 
 | ||||||
| 	kfree(dm); | 	kfree(dm); | ||||||
| 
 | 
 | ||||||
|  | 	/* start polling timestamp */ | ||||||
|  | 	if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) | ||||||
|  | 		gs_usb_timestamp_init(dev); | ||||||
|  | 
 | ||||||
| 	dev->can.state = CAN_STATE_ERROR_ACTIVE; | 	dev->can.state = CAN_STATE_ERROR_ACTIVE; | ||||||
| 
 | 
 | ||||||
| 	parent->active_channels++; | 	parent->active_channels++; | ||||||
|  | @ -858,6 +999,10 @@ static int gs_can_close(struct net_device *netdev) | ||||||
| 
 | 
 | ||||||
| 	netif_stop_queue(netdev); | 	netif_stop_queue(netdev); | ||||||
| 
 | 
 | ||||||
|  | 	/* stop polling timestamp */ | ||||||
|  | 	if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) | ||||||
|  | 		gs_usb_timestamp_stop(dev); | ||||||
|  | 
 | ||||||
| 	/* Stop polling */ | 	/* Stop polling */ | ||||||
| 	parent->active_channels--; | 	parent->active_channels--; | ||||||
| 	if (!parent->active_channels) { | 	if (!parent->active_channels) { | ||||||
|  | @ -890,11 +1035,22 @@ static int gs_can_close(struct net_device *netdev) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int gs_can_eth_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) | ||||||
|  | { | ||||||
|  | 	const struct gs_can *dev = netdev_priv(netdev); | ||||||
|  | 
 | ||||||
|  | 	if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) | ||||||
|  | 		return can_eth_ioctl_hwts(netdev, ifr, cmd); | ||||||
|  | 
 | ||||||
|  | 	return -EOPNOTSUPP; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static const struct net_device_ops gs_usb_netdev_ops = { | static const struct net_device_ops gs_usb_netdev_ops = { | ||||||
| 	.ndo_open = gs_can_open, | 	.ndo_open = gs_can_open, | ||||||
| 	.ndo_stop = gs_can_close, | 	.ndo_stop = gs_can_close, | ||||||
| 	.ndo_start_xmit = gs_can_start_xmit, | 	.ndo_start_xmit = gs_can_start_xmit, | ||||||
| 	.ndo_change_mtu = can_change_mtu, | 	.ndo_change_mtu = can_change_mtu, | ||||||
|  | 	.ndo_eth_ioctl = gs_can_eth_ioctl, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static int gs_usb_set_identify(struct net_device *netdev, bool do_identify) | static int gs_usb_set_identify(struct net_device *netdev, bool do_identify) | ||||||
|  | @ -944,9 +1100,21 @@ static int gs_usb_set_phys_id(struct net_device *dev, | ||||||
| 	return rc; | 	return rc; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int gs_usb_get_ts_info(struct net_device *netdev, | ||||||
|  | 			      struct ethtool_ts_info *info) | ||||||
|  | { | ||||||
|  | 	struct gs_can *dev = netdev_priv(netdev); | ||||||
|  | 
 | ||||||
|  | 	/* report if device supports HW timestamps */ | ||||||
|  | 	if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP) | ||||||
|  | 		return can_ethtool_op_get_ts_info_hwts(netdev, info); | ||||||
|  | 
 | ||||||
|  | 	return ethtool_op_get_ts_info(netdev, info); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static const struct ethtool_ops gs_usb_ethtool_ops = { | static const struct ethtool_ops gs_usb_ethtool_ops = { | ||||||
| 	.set_phys_id = gs_usb_set_phys_id, | 	.set_phys_id = gs_usb_set_phys_id, | ||||||
| 	.get_ts_info = ethtool_op_get_ts_info, | 	.get_ts_info = gs_usb_get_ts_info, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static struct gs_can *gs_make_candev(unsigned int channel, | static struct gs_can *gs_make_candev(unsigned int channel, | ||||||
|  | @ -1202,15 +1370,13 @@ static int gs_usb_probe(struct usb_interface *intf, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	init_usb_anchor(&dev->rx_submitted); | 	init_usb_anchor(&dev->rx_submitted); | ||||||
| 	/* default to classic CAN, switch to CAN-FD if at least one of
 |  | ||||||
| 	 * our channels support CAN-FD. |  | ||||||
| 	 */ |  | ||||||
| 	dev->hf_size_rx = struct_size(hf, classic_can, 1); |  | ||||||
| 
 | 
 | ||||||
| 	usb_set_intfdata(intf, dev); | 	usb_set_intfdata(intf, dev); | ||||||
| 	dev->udev = udev; | 	dev->udev = udev; | ||||||
| 
 | 
 | ||||||
| 	for (i = 0; i < icount; i++) { | 	for (i = 0; i < icount; i++) { | ||||||
|  | 		unsigned int hf_size_rx = 0; | ||||||
|  | 
 | ||||||
| 		dev->canch[i] = gs_make_candev(i, intf, dconf); | 		dev->canch[i] = gs_make_candev(i, intf, dconf); | ||||||
| 		if (IS_ERR_OR_NULL(dev->canch[i])) { | 		if (IS_ERR_OR_NULL(dev->canch[i])) { | ||||||
| 			/* save error code to return later */ | 			/* save error code to return later */ | ||||||
|  | @ -1228,8 +1394,21 @@ static int gs_usb_probe(struct usb_interface *intf, | ||||||
| 		} | 		} | ||||||
| 		dev->canch[i]->parent = dev; | 		dev->canch[i]->parent = dev; | ||||||
| 
 | 
 | ||||||
| 		if (dev->canch[i]->can.ctrlmode_supported & CAN_CTRLMODE_FD) | 		/* set RX packet size based on FD and if hardware
 | ||||||
| 			dev->hf_size_rx = struct_size(hf, canfd, 1); |                 * timestamps are supported. | ||||||
|  | 		*/ | ||||||
|  | 		if (dev->canch[i]->can.ctrlmode_supported & CAN_CTRLMODE_FD) { | ||||||
|  | 			if (dev->canch[i]->feature & GS_CAN_FEATURE_HW_TIMESTAMP) | ||||||
|  | 				hf_size_rx = struct_size(hf, canfd_ts, 1); | ||||||
|  | 			else | ||||||
|  | 				hf_size_rx = struct_size(hf, canfd, 1); | ||||||
|  | 		} else { | ||||||
|  | 			if (dev->canch[i]->feature & GS_CAN_FEATURE_HW_TIMESTAMP) | ||||||
|  | 				hf_size_rx = struct_size(hf, classic_can_ts, 1); | ||||||
|  | 			else | ||||||
|  | 				hf_size_rx = struct_size(hf, classic_can, 1); | ||||||
|  | 		} | ||||||
|  | 		dev->hf_size_rx = max(dev->hf_size_rx, hf_size_rx); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	kfree(dconf); | 	kfree(dconf); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 John Whittington
						John Whittington