/*
 * Copyright (c) 2023 Trackunit Corporation
 * Copyright (c) 2023 Bjarki Arge Andreasen
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file drivers/rtc.h
 * @brief Public real time clock driver API
 */

#ifndef ZEPHYR_INCLUDE_DRIVERS_RTC_H_
#define ZEPHYR_INCLUDE_DRIVERS_RTC_H_

/**
 * @brief RTC Interface
 * @defgroup rtc_interface RTC Interface
 * @since 3.4
 * @version 0.1.0
 * @ingroup io_interfaces
 * @{
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <errno.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Mask for alarm time fields to enable when setting alarm time
 * @name RTC Alarm Time Mask
 * @anchor RTC_ALARM_TIME_MASK
 * @{
 */
#define RTC_ALARM_TIME_MASK_SECOND	BIT(0)
#define RTC_ALARM_TIME_MASK_MINUTE	BIT(1)
#define RTC_ALARM_TIME_MASK_HOUR	BIT(2)
#define RTC_ALARM_TIME_MASK_MONTHDAY	BIT(3)
#define RTC_ALARM_TIME_MASK_MONTH	BIT(4)
#define RTC_ALARM_TIME_MASK_YEAR	BIT(5)
#define RTC_ALARM_TIME_MASK_WEEKDAY	BIT(6)
#define RTC_ALARM_TIME_MASK_YEARDAY	BIT(7)
#define RTC_ALARM_TIME_MASK_NSEC	BIT(8)
/**
 * @}
 */

/**
 * @brief Structure for storing date and time values with sub-second precision.
 *
 * @details The structure is 1-1 mapped to the struct tm for the members
 * \p tm_sec to \p tm_isdst making it compatible with the standard time library.
 *
 * @note Use \ref rtc_time_to_tm() to safely cast from a \ref rtc_time
 * pointer to a \ref tm pointer.
 */
struct rtc_time {
	int tm_sec;	/**< Seconds [0, 59] */
	int tm_min;	/**< Minutes [0, 59] */
	int tm_hour;	/**< Hours [0, 23] */
	int tm_mday;	/**< Day of the month [1, 31] */
	int tm_mon;	/**< Month [0, 11] */
	int tm_year;	/**< Year - 1900 */
	int tm_wday;	/**< Day of the week [0, 6] (Sunday = 0) (Unknown = -1) */
	int tm_yday;	/**< Day of the year [0, 365] (Unknown = -1) */
	int tm_isdst;	/**< Daylight saving time flag [-1] (Unknown = -1) */
	int tm_nsec;	/**< Nanoseconds [0, 999999999] (Unknown = 0) */
};

/**
 * @typedef rtc_update_callback
 * @brief RTC update event callback
 *
 * @param dev Device instance invoking the handler
 * @param user_data Optional user data provided when update irq callback is set
 */
typedef void (*rtc_update_callback)(const struct device *dev, void *user_data);

/**
 * @typedef rtc_alarm_callback
 * @brief RTC alarm triggered callback
 *
 * @param dev Device instance invoking the handler
 * @param id Alarm id
 * @param user_data Optional user data passed with the alarm configuration
 */
typedef void (*rtc_alarm_callback)(const struct device *dev, uint16_t id, void *user_data);

/**
 * @cond INTERNAL_HIDDEN
 *
 * For internal driver use only, skip these in public documentation.
 */

/**
 * @typedef rtc_api_set_time
 * @brief API for setting RTC time
 */
typedef int (*rtc_api_set_time)(const struct device *dev, const struct rtc_time *timeptr);

/**
 * @typedef rtc_api_get_time
 * @brief API for getting RTC time
 */
typedef int (*rtc_api_get_time)(const struct device *dev, struct rtc_time *timeptr);

/**
 * @typedef rtc_api_alarm_get_supported_fields
 * @brief API for getting the supported fields of the RTC alarm time
 */
typedef int (*rtc_api_alarm_get_supported_fields)(const struct device *dev, uint16_t id,
						  uint16_t *mask);

/**
 * @typedef rtc_api_alarm_set_time
 * @brief API for setting RTC alarm time
 */
typedef int (*rtc_api_alarm_set_time)(const struct device *dev, uint16_t id, uint16_t mask,
				      const struct rtc_time *timeptr);

/**
 * @typedef rtc_api_alarm_get_time
 * @brief API for getting RTC alarm time
 */
typedef int (*rtc_api_alarm_get_time)(const struct device *dev, uint16_t id, uint16_t *mask,
				      struct rtc_time *timeptr);

/**
 * @typedef rtc_api_alarm_is_pending
 * @brief API for testing if RTC alarm is pending
 */
typedef int (*rtc_api_alarm_is_pending)(const struct device *dev, uint16_t id);

/**
 * @typedef rtc_api_alarm_set_callback
 * @brief API for setting RTC alarm callback
 */
typedef int (*rtc_api_alarm_set_callback)(const struct device *dev, uint16_t id,
					  rtc_alarm_callback callback, void *user_data);

/**
 * @typedef rtc_api_update_set_callback
 * @brief API for setting RTC update callback
 */
typedef int (*rtc_api_update_set_callback)(const struct device *dev,
					   rtc_update_callback callback, void *user_data);

/**
 * @typedef rtc_api_set_calibration
 * @brief API for setting RTC calibration
 */
typedef int (*rtc_api_set_calibration)(const struct device *dev, int32_t calibration);

/**
 * @typedef rtc_api_get_calibration
 * @brief API for getting RTC calibration
 */
typedef int (*rtc_api_get_calibration)(const struct device *dev, int32_t *calibration);

/**
 * @brief RTC driver API
 */
__subsystem struct rtc_driver_api {
	rtc_api_set_time set_time;
	rtc_api_get_time get_time;
#if defined(CONFIG_RTC_ALARM) || defined(__DOXYGEN__)
	rtc_api_alarm_get_supported_fields alarm_get_supported_fields;
	rtc_api_alarm_set_time alarm_set_time;
	rtc_api_alarm_get_time alarm_get_time;
	rtc_api_alarm_is_pending alarm_is_pending;
	rtc_api_alarm_set_callback alarm_set_callback;
#endif /* CONFIG_RTC_ALARM */
#if defined(CONFIG_RTC_UPDATE) || defined(__DOXYGEN__)
	rtc_api_update_set_callback update_set_callback;
#endif /* CONFIG_RTC_UPDATE */
#if defined(CONFIG_RTC_CALIBRATION) || defined(__DOXYGEN__)
	rtc_api_set_calibration set_calibration;
	rtc_api_get_calibration get_calibration;
#endif /* CONFIG_RTC_CALIBRATION */
};

/** @endcond */

/**
 * @brief API for setting RTC time.
 *
 * @param dev Device instance
 * @param timeptr The time to set
 *
 * @return 0 if successful
 * @return -EINVAL if RTC time is invalid or exceeds hardware capabilities
 * @return -errno code if failure
 */
__syscall int rtc_set_time(const struct device *dev, const struct rtc_time *timeptr);

static inline int z_impl_rtc_set_time(const struct device *dev, const struct rtc_time *timeptr)
{
	return DEVICE_API_GET(rtc, dev)->set_time(dev, timeptr);
}

/**
 * @brief API for getting RTC time.
 *
 * @param dev Device instance
 * @param timeptr Destination for the time
 *
 * @return 0 if successful
 * @return -ENODATA if RTC time has not been set
 * @return -errno code if failure
 */
__syscall int rtc_get_time(const struct device *dev, struct rtc_time *timeptr);

static inline int z_impl_rtc_get_time(const struct device *dev, struct rtc_time *timeptr)
{
	return DEVICE_API_GET(rtc, dev)->get_time(dev, timeptr);
}

/**
 * @name RTC Interface Alarm
 * @{
 */
#if defined(CONFIG_RTC_ALARM) || defined(__DOXYGEN__)

/**
 * @brief API for getting the supported fields of the RTC alarm time.
 *
 * @param dev Device instance
 * @param id Id of the alarm
 * @param mask Mask of fields in the alarm time which are supported
 *
 * @note Bits in the mask param are defined here @ref RTC_ALARM_TIME_MASK.
 *
 * @return 0 if successful
 * @return -EINVAL if id is out of range or time is invalid
 * @return -ENOTSUP if API is not supported by hardware
 * @return -errno code if failure
 */
__syscall int rtc_alarm_get_supported_fields(const struct device *dev, uint16_t id,
					     uint16_t *mask);

static inline int z_impl_rtc_alarm_get_supported_fields(const struct device *dev, uint16_t id,
							uint16_t *mask)
{
	if (DEVICE_API_GET(rtc, dev)->alarm_get_supported_fields == NULL) {
		return -ENOSYS;
	}

	return DEVICE_API_GET(rtc, dev)->alarm_get_supported_fields(dev, id, mask);
}

/**
 * @brief API for setting RTC alarm time.
 *
 * @details To enable an RTC alarm, one or more fields of the RTC alarm time
 * must be enabled. The mask designates which fields of the RTC alarm time to
 * enable. If the mask parameter is 0, the alarm will be disabled. The RTC
 * alarm will trigger when all enabled fields of the alarm time match the RTC
 * time.
 *
 * @param dev Device instance
 * @param id Id of the alarm
 * @param mask Mask of fields in the alarm time to enable
 * @param timeptr The alarm time to set
 *
 * @note The timeptr param may be NULL if the mask param is 0
 * @note Only the enabled fields in the timeptr param need to be configured
 * @note Bits in the mask param are defined here @ref RTC_ALARM_TIME_MASK
 *
 * @return 0 if successful
 * @return -EINVAL if id is out of range or time is invalid
 * @return -ENOTSUP if API is not supported by hardware
 * @return -errno code if failure
 */
__syscall int rtc_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
				 const struct rtc_time *timeptr);

static inline int z_impl_rtc_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
					    const struct rtc_time *timeptr)
{
	if (DEVICE_API_GET(rtc, dev)->alarm_set_time == NULL) {
		return -ENOSYS;
	}

	return DEVICE_API_GET(rtc, dev)->alarm_set_time(dev, id, mask, timeptr);
}

/**
 * @brief API for getting RTC alarm time.
 *
 * @param dev Device instance
 * @param id Id of the alarm
 * @param mask Destination for mask of fields which are enabled in the alarm time
 * @param timeptr Destination for the alarm time
 *
 * @note Bits in the mask param are defined here @ref RTC_ALARM_TIME_MASK
 *
 * @return 0 if successful
 * @return -EINVAL if id is out of range
 * @return -ENOTSUP if API is not supported by hardware
 * @return -errno code if failure
 */
__syscall int rtc_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
				 struct rtc_time *timeptr);

static inline int z_impl_rtc_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
					    struct rtc_time *timeptr)
{
	if (DEVICE_API_GET(rtc, dev)->alarm_get_time == NULL) {
		return -ENOSYS;
	}

	return DEVICE_API_GET(rtc, dev)->alarm_get_time(dev, id, mask, timeptr);
}

/**
 * @brief API for testing if RTC alarm is pending.
 *
 * @details Test whether or not the alarm with id is pending. If the alarm
 * is pending, the pending status is cleared.
 *
 * @param dev Device instance
 * @param id Id of the alarm to test
 *
 * @return 1 if alarm was pending
 * @return 0 if alarm was not pending
 * @return -EINVAL if id is out of range
 * @return -ENOTSUP if API is not supported by hardware
 * @return -errno code if failure
 */
__syscall int rtc_alarm_is_pending(const struct device *dev, uint16_t id);

static inline int z_impl_rtc_alarm_is_pending(const struct device *dev, uint16_t id)
{
	if (DEVICE_API_GET(rtc, dev)->alarm_is_pending == NULL) {
		return -ENOSYS;
	}

	return DEVICE_API_GET(rtc, dev)->alarm_is_pending(dev, id);
}

/**
 * @brief API for setting alarm callback.
 *
 * @details Setting the alarm callback for an alarm, will enable the
 * alarm callback. When the callback for an alarm is enabled, the
 * alarm triggered event will invoke the callback, after which the
 * alarm pending status will be cleared automatically. The alarm will
 * remain enabled until manually disabled using
 * \ref rtc_alarm_set_time().
 *
 * To disable the alarm callback for an alarm, the \p callback and
 * \p user_data parameters must be set to NULL. When the alarm
 * callback for an alarm is disabled, the alarm triggered event will
 * set the alarm status to "pending". To check if the alarm status is
 * "pending", use \ref rtc_alarm_is_pending().
 *
 * @param dev Device instance
 * @param id Id of the alarm for which the callback shall be set
 * @param callback Callback called when alarm occurs
 * @param user_data Optional user data passed to callback
 *
 * @return 0 if successful
 * @return -EINVAL if id is out of range
 * @return -ENOTSUP if API is not supported by hardware
 * @return -errno code if failure
 */
__syscall int rtc_alarm_set_callback(const struct device *dev, uint16_t id,
				     rtc_alarm_callback callback, void *user_data);

static inline int z_impl_rtc_alarm_set_callback(const struct device *dev, uint16_t id,
						rtc_alarm_callback callback, void *user_data)
{
	if (DEVICE_API_GET(rtc, dev)->alarm_set_callback == NULL) {
		return -ENOSYS;
	}

	return DEVICE_API_GET(rtc, dev)->alarm_set_callback(dev, id, callback, user_data);
}

#endif /* CONFIG_RTC_ALARM */
/**
 * @}
 */

/**
 * @name RTC Interface Update
 * @{
 */
#if defined(CONFIG_RTC_UPDATE) || defined(__DOXYGEN__)

/**
 * @brief API for setting update callback.
 *
 * @details Setting the update callback will enable the update
 * callback. The update callback will be invoked every time the
 * RTC clock is updated by 1 second. It can be used to
 * synchronize the RTC clock with other clock sources.
 *
 * To disable the update callback for the RTC clock, the
 * \p callback and \p user_data parameters must be set to NULL.
 *
 * @param dev Device instance
 * @param callback Callback called when update occurs
 * @param user_data Optional user data passed to callback
 *
 * @return 0 if successful
 * @return -ENOTSUP if API is not supported by hardware
 * @return -errno code if failure
 */
__syscall int rtc_update_set_callback(const struct device *dev, rtc_update_callback callback,
				      void *user_data);

static inline int z_impl_rtc_update_set_callback(const struct device *dev,
						 rtc_update_callback callback, void *user_data)
{
	if (DEVICE_API_GET(rtc, dev)->update_set_callback == NULL) {
		return -ENOSYS;
	}

	return DEVICE_API_GET(rtc, dev)->update_set_callback(dev, callback, user_data);
}

#endif /* CONFIG_RTC_UPDATE */
/**
 * @}
 */

/**
 * @name RTC Interface Calibration
 * @{
 */
#if defined(CONFIG_RTC_CALIBRATION) || defined(__DOXYGEN__)

/**
 * @brief API for setting RTC calibration.
 *
 * @details Calibration is applied to the RTC clock input. A
 * positive calibration value will increase the frequency of
 * the RTC clock, a negative value will decrease the
 * frequency of the RTC clock.
 *
 * @see rtc_calibration_from_frequency()
 *
 * @param dev Device instance
 * @param calibration Calibration to set in parts per billion
 *
 * @return 0 if successful
 * @return -EINVAL if calibration is out of range
 * @return -ENOTSUP if API is not supported by hardware
 * @return -errno code if failure
 */
__syscall int rtc_set_calibration(const struct device *dev, int32_t calibration);

static inline int z_impl_rtc_set_calibration(const struct device *dev, int32_t calibration)
{
	if (DEVICE_API_GET(rtc, dev)->set_calibration == NULL) {
		return -ENOSYS;
	}

	return DEVICE_API_GET(rtc, dev)->set_calibration(dev, calibration);
}

/**
 * @brief API for getting RTC calibration.
 *
 * @param dev Device instance
 * @param calibration Destination for calibration in parts per billion
 *
 * @return 0 if successful
 * @return -ENOTSUP if API is not supported by hardware
 * @return -errno code if failure
 */
__syscall int rtc_get_calibration(const struct device *dev, int32_t *calibration);

static inline int z_impl_rtc_get_calibration(const struct device *dev, int32_t *calibration)
{
	if (DEVICE_API_GET(rtc, dev)->get_calibration == NULL) {
		return -ENOSYS;
	}

	return DEVICE_API_GET(rtc, dev)->get_calibration(dev, calibration);
}

#endif /* CONFIG_RTC_CALIBRATION */
/**
 * @}
 */

/**
 * @name RTC Interface Helpers
 * @{
 */

/**
 * @brief Forward declaration of struct tm for \ref rtc_time_to_tm().
 */
struct tm;

/**
 * @brief Convenience function for safely casting a \ref rtc_time pointer
 * to a \ref tm pointer.
 */
static inline struct tm *rtc_time_to_tm(struct rtc_time *timeptr)
{
	return (struct tm *)timeptr;
}

/**
 * @brief Determine required calibration to 1 Hertz from frequency.
 *
 * @param frequency Frequency of the RTC in nano Hertz
 *
 * @return The required calibration in parts per billion
 */
static inline int32_t rtc_calibration_from_frequency(uint32_t frequency)
{
	__ASSERT_NO_MSG(frequency > 0);

	return (int32_t)((1000000000000000000LL / frequency) - 1000000000);
}

/**
 * @}
 */

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#include <zephyr/syscalls/rtc.h>

#endif /* ZEPHYR_INCLUDE_DRIVERS_RTC_H_ */
