Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2018 Xilinx, Inc.
 * SPDX-License-Identifier: Apache-2.0
 */

#include <drivers/timer/system_timer.h>
#include <sys_clock.h>
#include "irq.h"
#include "legacy_api.h"

#define TIMER_FREQ          CONFIG_SYS_CLOCK_TICKS_PER_SEC

#if (CONFIG_XLNX_PSTTC_TIMER_INDEX == 0)
#define TIMER_INPUT_CLKHZ   DT_INST_0_CDNS_TTC_CLOCK_FREQUENCY
#define TIMER_IRQ           DT_INST_0_CDNS_TTC_IRQ_0
#define TIMER_BASEADDR      DT_INST_0_CDNS_TTC_BASE_ADDRESS
#else
#error ("No timer is specified")
#endif

#define XTTCPS_CLK_CNTRL_OFFSET	   0x00000000U  /**< Clock Control Register */
#define XTTCPS_CNT_CNTRL_OFFSET	   0x0000000CU  /**< Counter Control Register*/
#define XTTCPS_COUNT_VALUE_OFFSET  0x00000018U  /**< Current Counter Value */
#define XTTCPS_INTERVAL_VAL_OFFSET 0x00000024U  /**< Interval Count Value */
#define XTTCPS_MATCH_0_OFFSET	   0x00000030U  /**< Match 1 value */
#define XTTCPS_MATCH_1_OFFSET	   0x0000003CU  /**< Match 2 value */
#define XTTCPS_MATCH_2_OFFSET	   0x00000048U  /**< Match 3 value */
#define XTTCPS_ISR_OFFSET	   0x00000054U  /**< Interrupt Status Register */
#define XTTCPS_IER_OFFSET	   0x00000060U  /**< Interrupt Enable Register */

/* Clock Control Register definitions */
#define XTTCPS_CLK_CNTRL_PS_EN_MASK	0x00000001U  /**< Prescale enable */
#define XTTCPS_CLK_CNTRL_PS_VAL_MASK	0x0000001EU  /**< Prescale value */
#define XTTCPS_CLK_CNTRL_PS_VAL_SHIFT	1U  /**< Prescale shift */
#define XTTCPS_CLK_CNTRL_PS_DISABLE	16U  /**< Prescale disable */
#define XTTCPS_CLK_CNTRL_SRC_MASK	0x00000020U  /**< Clock source */
#define XTTCPS_CLK_CNTRL_EXT_EDGE_MASK	0x00000040U  /**< External Clock edge */

/* Counter Control Register definitions */
#define XTTCPS_CNT_CNTRL_DIS_MASK	0x00000001U /**< Disable the counter */
#define XTTCPS_CNT_CNTRL_INT_MASK	0x00000002U /**< Interval mode */
#define XTTCPS_CNT_CNTRL_DECR_MASK	0x00000004U /**< Decrement mode */
#define XTTCPS_CNT_CNTRL_MATCH_MASK	0x00000008U /**< Match mode */
#define XTTCPS_CNT_CNTRL_RST_MASK	0x00000010U /**< Reset counter */
#define XTTCPS_CNT_CNTRL_EN_WAVE_MASK	0x00000020U /**< Enable waveform */
#define XTTCPS_CNT_CNTRL_POL_WAVE_MASK	0x00000040U /**< Waveform polarity */
#define XTTCPS_CNT_CNTRL_RESET_VALUE	0x00000021U /**< Reset value */

/* Interrupt register masks */
#define XTTCPS_IXR_INTERVAL_MASK	0x00000001U  /**< Interval Interrupt */
#define XTTCPS_IXR_MATCH_0_MASK		0x00000002U  /**< Match 1 Interrupt */
#define XTTCPS_IXR_MATCH_1_MASK		0x00000004U  /**< Match 2 Interrupt */
#define XTTCPS_IXR_MATCH_2_MASK		0x00000008U  /**< Match 3 Interrupt */
#define XTTCPS_IXR_CNT_OVR_MASK		0x00000010U  /**< Counter Overflow */
#define XTTCPS_IXR_ALL_MASK		0x0000001FU  /**< All valid Interrupts */

#define XTTC_MAX_INTERVAL_COUNT 0xFFFFFFFFU /**< Maximum value of interval counter */

static u32_t accumulated_cycles;
static s32_t _sys_idle_elapsed_ticks = 1;

static int xttc_calculate_interval(u32_t *interval, u8_t *prescaler)
{
	u32_t tmpinterval = 0;
	u8_t tmpprescaler = 0;
	unsigned int tmpval;

	tmpval = (u32_t)(TIMER_INPUT_CLKHZ / TIMER_FREQ);

	if (tmpval < (u32_t)65536U) {
		/* no prescaler is required */
		tmpinterval = tmpval;
		tmpprescaler = 0;
	} else {
		for (tmpprescaler = 1U; tmpprescaler < 16; tmpprescaler++) {
			tmpval = (u32_t)(TIMER_INPUT_CLKHZ /
					 (TIMER_FREQ * (1U << tmpprescaler)));
			if (tmpval < (u32_t)65536U) {
				tmpinterval = tmpval;
				break;
			}
		}
	}

	if (tmpinterval != 0) {
		*interval = tmpinterval;
		*prescaler = tmpprescaler;
		return 0;
	}

	/* TBD: Is there a way to adjust the sys clock parameters such as
	 * ticks per sec if it failed to configure the timer as specified
	 */
	return -EINVAL;
}

/**
 * @brief System timer tick handler
 *
 * This routine handles the system clock tick interrupt. A TICK_EVENT event
 * is pushed onto the kernel stack.
 *
 * The symbol for this routine is either _timer_int_handler.
 *
 * @return N/A
 */
void _timer_int_handler(void *unused)
{
	ARG_UNUSED(unused);

	u32_t regval;

	regval = sys_read32(TIMER_BASEADDR + XTTCPS_ISR_OFFSET);
	accumulated_cycles += sys_clock_hw_cycles_per_tick();
	z_clock_announce(_sys_idle_elapsed_ticks);
}

/**
 * @brief Initialize and enable the system clock
 *
 * This routine is used to program the systick to deliver interrupts at the
 * rate specified via the 'sys_clock_us_per_tick' global variable.
 *
 * @return 0
 */
int z_clock_driver_init(struct device *device)
{
	int ret;
	u32_t interval;
	u8_t prescaler;
	u32_t regval;

	/* Stop timer */
	sys_write32(XTTCPS_CNT_CNTRL_DIS_MASK,
		    TIMER_BASEADDR + XTTCPS_CNT_CNTRL_OFFSET);

	/* Calculate prescaler */
	ret = xttc_calculate_interval(&interval, &prescaler);
	if (ret < 0) {
		printk("Failed to calculate prescaler.\n");
		return ret;
	}

	/* Reset registers */
	sys_write32(XTTCPS_CNT_CNTRL_RESET_VALUE,
		    TIMER_BASEADDR + XTTCPS_CNT_CNTRL_OFFSET);
	sys_write32(0, TIMER_BASEADDR + XTTCPS_CLK_CNTRL_OFFSET);
	sys_write32(0, TIMER_BASEADDR + XTTCPS_INTERVAL_VAL_OFFSET);
	sys_write32(0, TIMER_BASEADDR + XTTCPS_MATCH_0_OFFSET);
	sys_write32(0, TIMER_BASEADDR + XTTCPS_MATCH_1_OFFSET);
	sys_write32(0, TIMER_BASEADDR + XTTCPS_MATCH_2_OFFSET);
	sys_write32(0, TIMER_BASEADDR + XTTCPS_IER_OFFSET);
	sys_write32(XTTCPS_IXR_ALL_MASK, TIMER_BASEADDR + XTTCPS_ISR_OFFSET);
	/* Reset counter value */
	regval = sys_read32(TIMER_BASEADDR + XTTCPS_CNT_CNTRL_OFFSET);
	regval |= XTTCPS_CNT_CNTRL_RST_MASK;
	sys_write32(regval, TIMER_BASEADDR + XTTCPS_CNT_CNTRL_OFFSET);

	/* Set options */
	regval = sys_read32(TIMER_BASEADDR + XTTCPS_CNT_CNTRL_OFFSET);
	regval |= XTTCPS_CNT_CNTRL_INT_MASK;
	sys_write32(regval, TIMER_BASEADDR + XTTCPS_CNT_CNTRL_OFFSET);

	/* Set interval and prescaller */
	sys_write32(interval, TIMER_BASEADDR + XTTCPS_INTERVAL_VAL_OFFSET);
	regval = (u32_t)((prescaler & 0xFU) << 1);
	sys_write32(regval, TIMER_BASEADDR + XTTCPS_CLK_CNTRL_OFFSET);

	/* Enable timer interrupt */
	IRQ_CONNECT(TIMER_IRQ, 0, _timer_int_handler, 0, 0);
	irq_enable(TIMER_IRQ);
	regval = sys_read32(TIMER_BASEADDR + XTTCPS_IER_OFFSET);
	regval |= XTTCPS_IXR_INTERVAL_MASK;
	sys_write32(regval, TIMER_BASEADDR + XTTCPS_IER_OFFSET);

	/* Start timer */
	regval = sys_read32(TIMER_BASEADDR + XTTCPS_CNT_CNTRL_OFFSET);
	regval &= (~XTTCPS_CNT_CNTRL_DIS_MASK);
	sys_write32(regval, TIMER_BASEADDR + XTTCPS_CNT_CNTRL_OFFSET);

	return 0;
}


/**
 * @brief Read the platform's timer hardware
 *
 * This routine returns the current time in terms of timer hardware clock
 * cycles.
 *
 * @return up counter of elapsed clock cycles
 */
u32_t z_timer_cycle_get_32(void)
{
	return accumulated_cycles;
}