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) 2020 IoT.bzh <julien.massot@iot.bzh>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <soc.h>
#include <drivers/timer/system_timer.h>
#include <drivers/clock_control.h>
#include <drivers/clock_control/rcar_clock_control.h>

#define DT_DRV_COMPAT renesas_rcar_cmt

#define TIMER_IRQ              DT_INST_IRQN(0)
#define TIMER_BASE_ADDR        DT_INST_REG_ADDR(0)
#define TIMER_CLOCK_FREQUENCY  DT_INST_PROP(0, clock_frequency)

#define CLOCK_SUBSYS           DT_INST_CLOCKS_CELL(0, module)

#define CYCLES_PER_SEC         TIMER_CLOCK_FREQUENCY
#define CYCLES_PER_TICK        (CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)

static struct rcar_cpg_clk mod_clk = {
	.module = DT_INST_CLOCKS_CELL(0, module),
	.domain = DT_INST_CLOCKS_CELL(0, domain),
};

BUILD_ASSERT(CYCLES_PER_TICK > 1,
	     "CYCLES_PER_TICK must be greater than 1");

#define CMCOR0_OFFSET                   0x018   /* constant register 0 */
#define CMCNT0_OFFSET                   0x014   /* counter 0 */
#define CMCSR0_OFFSET                   0x010   /* control/status register 0 */

#define CMCOR1_OFFSET                   0x118   /* constant register 1 */
#define CMCNT1_OFFSET                   0x114   /* counter 1 */
#define CMCSR1_OFFSET                   0x110   /* control/status register 1 */

#define CMCLKE                          0xB00   /* CLK enable register */
#define CLKEN0                          BIT(5)  /* Enable Clock for channel 0 */
#define CLKEN1                          BIT(6)  /* Enable Clock for channel 1 */

#define CMSTR0_OFFSET                   0x000   /* Timer start register 0 */
#define CMSTR1_OFFSET                   0x100   /* Timer start register 1 */
#define START_BIT                       BIT(0)

#define CSR_CLK_DIV_1                   0x00000007
#define CSR_ENABLE_COUNTER_IN_DEBUG     BIT(3)
#define CSR_ENABLE_INTERRUPT            BIT(5)
#define CSR_FREE_RUN                    BIT(8)
#define CSR_WRITE_FLAG                  BIT(13)
#define CSR_OVERFLOW_FLAG               BIT(14)
#define CSR_MATCH_FLAG                  BIT(15)

static void cmt_isr(void *arg)
{
	ARG_UNUSED(arg);
	uint32_t reg_val;

	/* clear the interrupt */
	reg_val = sys_read32(TIMER_BASE_ADDR + CMCSR0_OFFSET);
	reg_val &= ~CSR_MATCH_FLAG;
	sys_write32(reg_val, TIMER_BASE_ADDR + CMCSR0_OFFSET);

	/* Announce to the kernel */
	sys_clock_announce(1);
}

/*
 * Initialize both channels at same frequency,
 * Set the first one to generates interrupt at CYCLES_PER_TICK.
 * The second one is used for cycles count, the match value is set
 * at max uint32_t.
 */
int sys_clock_driver_init(const struct device *device)
{
	const struct device *clk;
	uint32_t reg_val;
	int i, ret;

	ARG_UNUSED(device);
	clk = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(0));
	if (clk == NULL) {
		return -ENODEV;
	}

	ret = clock_control_on(clk, (clock_control_subsys_t *)&mod_clk);
	if (ret < 0) {
		return ret;
	}

	/* Supply clock for both channels */
	sys_write32(CLKEN0 | CLKEN1, TIMER_BASE_ADDR + CMCLKE);

	/* Stop both channels */
	reg_val = sys_read32(TIMER_BASE_ADDR + CMSTR0_OFFSET);
	reg_val &= ~START_BIT;
	sys_write32(reg_val, TIMER_BASE_ADDR + CMSTR0_OFFSET);

	reg_val = sys_read32(TIMER_BASE_ADDR + CMSTR1_OFFSET);
	reg_val &= ~START_BIT;
	sys_write32(reg_val, TIMER_BASE_ADDR + CMSTR1_OFFSET);

	/* Set the timers as 32-bit, with RCLK/1 clock */
	sys_write32(CSR_FREE_RUN | CSR_CLK_DIV_1 | CSR_ENABLE_INTERRUPT,
		    TIMER_BASE_ADDR + CMCSR0_OFFSET);

	/* Do not enable interrupts for the second channel */
	sys_write32(CSR_FREE_RUN | CSR_CLK_DIV_1,
		    TIMER_BASE_ADDR + CMCSR1_OFFSET);

	/* Set the first channel match to CYCLES Per tick*/
	sys_write32(CYCLES_PER_TICK, TIMER_BASE_ADDR + CMCOR0_OFFSET);

	/* Set the second channel match to max uint32 */
	sys_write32(0xffffffff, TIMER_BASE_ADDR + CMCOR1_OFFSET);

	/* Reset the counter for first channel, check WRFLG first */
	while (sys_read32(TIMER_BASE_ADDR + CMCSR0_OFFSET) & CSR_WRITE_FLAG)
		;
	sys_write32(0, TIMER_BASE_ADDR + CMCNT0_OFFSET);

	for (i = 0; i < 1000; i++) {
		if (!sys_read32(TIMER_BASE_ADDR + CMCNT0_OFFSET)) {
			break;
		}
	}

	__ASSERT(sys_read32(TIMER_BASE_ADDR + CMCNT0_OFFSET) == 0,
			"Fail to clear CMCNT0 register");

	/* Connect timer interrupt for channel 0*/
	IRQ_CONNECT(TIMER_IRQ, 0, cmt_isr, 0, 0);
	irq_enable(TIMER_IRQ);

	/* Start the timers */
	sys_write32(START_BIT, TIMER_BASE_ADDR + CMSTR0_OFFSET);
	sys_write32(START_BIT, TIMER_BASE_ADDR + CMSTR1_OFFSET);

	return 0;
}

uint32_t sys_clock_elapsed(void)
{
	/* Always return 0 for tickful operation */
	return 0;
}

uint32_t sys_clock_cycle_get_32(void)
{
	return sys_read32(TIMER_BASE_ADDR + CMCNT1_OFFSET);
}