// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) Amazon.com, Inc. or its affiliates.
 * All rights reserved.
 */

#include <linux/pci.h>
#include "ena_netdev.h"
#include "ena_phc.h"
#include "ena_devlink.h"

#ifdef ENA_PHC_SUPPORT
#ifdef ENA_PHC_SUPPORT_ADJFREQ
static int ena_phc_adjfreq(struct ptp_clock_info *clock_info, s32 ppb)
{
	return -EOPNOTSUPP;
}

#endif /* ENA_PHC_SUPPORT_ADJFREQ */
static int ena_phc_adjtime(struct ptp_clock_info *clock_info, s64 delta)
{
	return -EOPNOTSUPP;
}

#ifdef ENA_PHC_SUPPORT_ADJFINE
static int ena_phc_adjfine(struct ptp_clock_info *clock_info, long scaled_ppm)
{
	return -EOPNOTSUPP;
}

#endif /* ENA_PHC_SUPPORT_ADJFINE */
static int ena_phc_feature_enable(struct ptp_clock_info *clock_info,
				  struct ptp_clock_request *rq,
				  int on)
{
	return -EOPNOTSUPP;
}

#ifdef ENA_PHC_SUPPORT_GETTIME64
#ifdef ENA_PHC_SUPPORT_GETTIME64_EXTENDED
static int ena_phc_gettimex64(struct ptp_clock_info *clock_info,
			      struct timespec64 *ts,
			      struct ptp_system_timestamp *sts)
{
	struct ena_phc_info *phc_info =
		container_of(clock_info, struct ena_phc_info, clock_info);
	unsigned long flags;
	u64 timestamp_nsec;
	int rc;

	spin_lock_irqsave(&phc_info->lock, flags);

	ptp_read_system_prets(sts);

	rc = ena_com_phc_get_timestamp(phc_info->adapter->ena_dev,
				       &timestamp_nsec);

	ptp_read_system_postts(sts);

	spin_unlock_irqrestore(&phc_info->lock, flags);

	*ts = ns_to_timespec64(timestamp_nsec);

	return rc;
}

#else /* ENA_PHC_SUPPORT_GETTIME64_EXTENDED */
static int ena_phc_gettime64(struct ptp_clock_info *clock_info, struct timespec64 *ts)
{
	struct ena_phc_info *phc_info =
		container_of(clock_info, struct ena_phc_info, clock_info);
	unsigned long flags;
	u64 timestamp_nsec;
	int rc;

	spin_lock_irqsave(&phc_info->lock, flags);

	rc = ena_com_phc_get_timestamp(phc_info->adapter->ena_dev,
				       &timestamp_nsec);

	spin_unlock_irqrestore(&phc_info->lock, flags);

	*ts = ns_to_timespec64(timestamp_nsec);

	return rc;
}

#endif /* ENA_PHC_SUPPORT_GETTIME64_EXTENDED */
static int ena_phc_settime64(struct ptp_clock_info *clock_info,
			     const struct timespec64 *ts)
{
	return -EOPNOTSUPP;
}

#else /* ENA_PHC_SUPPORT_GETTIME64 */
static int ena_phc_gettime(struct ptp_clock_info *clock_info, struct timespec *ts)
{
	struct ena_phc_info *phc_info =
		container_of(clock_info, struct ena_phc_info, clock_info);
	unsigned long flags;
	u64 timestamp_nsec;
	u32 remainder;
	int rc;

	spin_lock_irqsave(&phc_info->lock, flags);

	rc = ena_com_phc_get_timestamp(phc_info->adapter->ena_dev,
				       &timestamp_nsec);

	spin_unlock_irqrestore(&phc_info->lock, flags);

	ts->tv_sec = div_u64_rem(timestamp_nsec, NSEC_PER_SEC, &remainder);
	ts->tv_nsec = remainder;

	return rc;
}

static int ena_phc_settime(struct ptp_clock_info *clock_info, const struct timespec *ts)
{
	return -EOPNOTSUPP;
}

#endif /* ENA_PHC_SUPPORT_GETTIME64 */
static struct ptp_clock_info ena_ptp_clock_info = {
	.owner		= THIS_MODULE,
	.n_alarm	= 0,
	.n_ext_ts	= 0,
	.n_per_out	= 0,
	.pps		= 0,
#ifdef ENA_PHC_SUPPORT_ADJFREQ
	.adjfreq	= ena_phc_adjfreq,
#endif /* ENA_PHC_SUPPORT_ADJFREQ */
	.adjtime	= ena_phc_adjtime,
#ifdef ENA_PHC_SUPPORT_ADJFINE
	.adjfine	= ena_phc_adjfine,
#endif /* ENA_PHC_SUPPORT_ADJFINE */
#ifdef ENA_PHC_SUPPORT_GETTIME64
#ifdef ENA_PHC_SUPPORT_GETTIME64_EXTENDED
	.gettimex64	= ena_phc_gettimex64,
#else /* ENA_PHC_SUPPORT_GETTIME64_EXTENDED */
	.gettime64	= ena_phc_gettime64,
#endif /* ENA_PHC_SUPPORT_GETTIME64_EXTENDED */
	.settime64	= ena_phc_settime64,
#else /* ENA_PHC_SUPPORT_GETTIME64 */
	.gettime	= ena_phc_gettime,
	.settime	= ena_phc_settime,
#endif /* ENA_PHC_SUPPORT_GETTIME64 */
	.enable		= ena_phc_feature_enable,
};

/* Enable/Disable PHC by the kernel, affects on the next init flow */
void ena_phc_enable(struct ena_adapter *adapter, bool enable)
{
	struct ena_phc_info *phc_info = adapter->phc_info;

	if (!phc_info) {
		netdev_err(adapter->netdev, "phc_info is not allocated\n");
		return;
	}

	phc_info->enabled = enable;
}

/* Check if PHC is enabled by the kernel */
bool ena_phc_is_enabled(struct ena_adapter *adapter)
{
	struct ena_phc_info *phc_info = adapter->phc_info;

	return (phc_info && phc_info->enabled);
}

/* PHC is activated if ptp clock is registered in the kernel */
bool ena_phc_is_active(struct ena_adapter *adapter)
{
	struct ena_phc_info *phc_info = adapter->phc_info;

	return (phc_info && phc_info->clock);
}

static int ena_phc_register(struct ena_adapter *adapter)
{
	struct pci_dev *pdev = adapter->pdev;
	struct ptp_clock_info *clock_info;
	struct ena_phc_info *phc_info;
	int rc = 0;

	phc_info = adapter->phc_info;
	clock_info = &phc_info->clock_info;

	/* PHC may already be registered in case of a reset */
	if (ena_phc_is_active(adapter))
		return 0;

	phc_info->adapter = adapter;

	spin_lock_init(&phc_info->lock);

	/* Fill the ptp_clock_info struct and register PTP clock */
	*clock_info = ena_ptp_clock_info;
	snprintf(clock_info->name,
		 sizeof(clock_info->name),
		 "ena-ptp-%02x",
		 PCI_SLOT(pdev->devfn));

	phc_info->clock = ptp_clock_register(clock_info, &pdev->dev);
	if (IS_ERR(phc_info->clock)) {
		rc = PTR_ERR(phc_info->clock);
		netdev_err(adapter->netdev, "Failed registering ptp clock, error: %d\n",
			   rc);
		phc_info->clock = NULL;
	}

	return rc;
}

static void ena_phc_unregister(struct ena_adapter *adapter)
{
	struct ena_phc_info *phc_info = adapter->phc_info;

	/* During reset flow, PHC must stay registered
	 * to keep kernel's PHC index
	 */
	if (ena_phc_is_active(adapter) &&
	    !test_bit(ENA_FLAG_TRIGGER_RESET, &adapter->flags)) {
		ptp_clock_unregister(phc_info->clock);
		phc_info->clock = NULL;
	}
}

int ena_phc_alloc(struct ena_adapter *adapter)
{
	/* Allocate driver specific PHC info */
	adapter->phc_info = vzalloc(sizeof(*adapter->phc_info));
	if (unlikely(!adapter->phc_info)) {
		netdev_err(adapter->netdev, "Failed to alloc phc_info\n");
		return -ENOMEM;
	}

	return 0;
}

void ena_phc_free(struct ena_adapter *adapter)
{
	if (adapter->phc_info) {
		vfree(adapter->phc_info);
		adapter->phc_info = NULL;
	}
}

int ena_phc_init(struct ena_adapter *adapter)
{
	struct ena_com_dev *ena_dev = adapter->ena_dev;
	struct net_device *netdev = adapter->netdev;
	int rc = -EOPNOTSUPP;

	/* Validate PHC feature is supported in the device */
	if (!ena_com_phc_supported(ena_dev)) {
		netdev_dbg(netdev, "PHC feature is not supported by the device\n");
		goto err_ena_com_phc_init;
	}

	/* Validate PHC feature is enabled by the kernel */
	if (!ena_phc_is_enabled(adapter)) {
		netdev_dbg(netdev, "PHC feature is not enabled by the kernel\n");
		goto err_ena_com_phc_init;
	}

	/* Initialize device specific PHC info */
	rc = ena_com_phc_init(ena_dev);
	if (unlikely(rc)) {
		netdev_err(netdev, "Failed to init phc, error: %d\n", rc);
		goto err_ena_com_phc_init;
	}

	/* Configure PHC feature in driver and device */
	rc = ena_com_phc_config(ena_dev);
	if (unlikely(rc)) {
		netdev_err(netdev, "Failed to config phc, error: %d\n", rc);
		goto err_ena_com_phc_config;
	}

	/* Register to PTP class driver */
	rc = ena_phc_register(adapter);
	if (unlikely(rc)) {
		netdev_err(netdev, "Failed to register phc, error: %d\n", rc);
		goto err_ena_com_phc_config;
	}

	return 0;

err_ena_com_phc_config:
	ena_com_phc_destroy(ena_dev);
err_ena_com_phc_init:
	ena_phc_enable(adapter, false);
	ena_devlink_disable_phc_param(adapter->devlink);
	return rc;
}

void ena_phc_destroy(struct ena_adapter *adapter)
{
	ena_phc_unregister(adapter);
	ena_com_phc_destroy(adapter->ena_dev);
}

int ena_phc_get_index(struct ena_adapter *adapter)
{
	if (ena_phc_is_active(adapter))
		return ptp_clock_index(adapter->phc_info->clock);

	return -1;
}

int ena_phc_get_error_bound(struct ena_adapter *adapter, u32 *error_bound_nsec)
{
	if (!ena_phc_is_active(adapter))
		return -EOPNOTSUPP;

	return ena_com_phc_get_error_bound(adapter->ena_dev, error_bound_nsec);
}
#endif /* ENA_PHC_SUPPORT */
