Linux preempt-rt

Check our new training course

Real-Time Linux with PREEMPT_RT

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

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2020 IoT.bzh
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT renesas_rcar_cpg_mssr
#include <errno.h>
#include <soc.h>
#include <drivers/clock_control.h>
#include <drivers/clock_control/rcar_clock_control.h>
#include <dt-bindings/clock/renesas_rcar_cpg.h>

struct rcar_mssr_config {
	uint32_t base_address;
};

#define DEV_CFG(dev)  ((struct rcar_mssr_config *)(dev->config))

static const uint16_t rmstpsr[] = {
	0x110, 0x114, 0x118, 0x11c,
	0x120, 0x124, 0x128, 0x12c,
	0x980, 0x984, 0x988, 0x98c,
};

#define RMSTPSR(i)      rmstpsr[i]

/* Software Reset Register offsets */
static const uint16_t srcr[] = {
	0x0A0, 0x0A8, 0x0B0, 0x0B8,
	0x0BC, 0x0C4, 0x1C8, 0x1CC,
	0x920, 0x924, 0x928, 0x92C,
};

#define SRCR(i)         srcr[i]
#define SRSTCLR(i)      (0x940 + (i) * 4)

/* CPG write protect */
#define CPGWPR                    0x0900
/* CAN-FD Clock Frequency Control Register */
#define CANFDCKCR                 0x244

/* Clock stop bit */
#define CANFDCKCR_CKSTP           BIT(8)

/* On H3,M3,E3 parent clock of CANFD has 800MHz rate */
#define CANFDCKCR_PARENT_CLK_RATE 800000000
#define CANFDCKCR_DIVIDER_MASK    0x1FF

#define S3D4_CLK_RATE             66600000

static void cpg_write(const struct rcar_mssr_config *config,
		      uint32_t reg, uint32_t val)
{
	sys_write32(~val, config->base_address + CPGWPR);
	sys_write32(val, config->base_address + reg);
	/* Wait for at least one cycle of the RCLK clock (@ ca. 32 kHz) */
	k_sleep(K_USEC(35));
}

static void cpg_reset(const struct rcar_mssr_config *config,
		      uint32_t reg, uint32_t bit)
{
	cpg_write(config, SRCR(reg), BIT(bit));
	cpg_write(config, SRSTCLR(reg), BIT(bit));
}

static int cpg_core_clock_endisable(const struct device *dev,
				    uint32_t module, uint32_t rate, bool enable)
{
	const struct rcar_mssr_config *config = DEV_CFG(dev);
	uint32_t divider;
	unsigned int key;
	int ret;

	/* Only support CANFD core clock at the moment */
	if (module != CPG_CORE_CLK_CANFD) {
		return -EINVAL;
	}

	key = irq_lock();

	if (enable) {
		if ((CANFDCKCR_PARENT_CLK_RATE % rate) != 0) {
			__ASSERT(true, "Can not generate "
				 "%u from CANFD parent clock", rate);
			ret = -EINVAL;
			goto unlock;
		}
		divider = (CANFDCKCR_PARENT_CLK_RATE / rate) - 1;
		if (divider > CANFDCKCR_DIVIDER_MASK) {
			__ASSERT(true, "Can not generate %u "
				 "from CANFD parent clock", rate);
			ret = -EINVAL;
			goto unlock;
		}
		cpg_write(config, CANFDCKCR, divider);
	} else {
		cpg_write(config, CANFDCKCR, CANFDCKCR_CKSTP);
	}

unlock:
	irq_unlock(key);
	return 0;
}

static int cpg_rmstp_clock_endisable(const struct device *dev,
				     uint32_t module, bool enable)
{
	const struct rcar_mssr_config *config = DEV_CFG(dev);
	uint32_t reg = module / 100;
	uint32_t bit = module % 100;
	uint32_t bitmask = BIT(bit);
	uint32_t reg_val;

	__ASSERT((bit < 32) && reg < ARRAY_SIZE(rmstpsr),
		 "Invalid module number for cpg clock: %d", module);

	unsigned int key = irq_lock();

	reg_val = sys_read32(config->base_address + RMSTPSR(reg));
	if (enable) {
		reg_val &= ~bitmask;
	} else {
		reg_val |= bitmask;
	}

	sys_write32(reg_val, config->base_address + RMSTPSR(reg));
	if (!enable) {
		cpg_reset(config, reg, bit);
	}

	irq_unlock(key);

	return 0;
}

static int cpg_mssr_blocking_start(const struct device *dev,
			       clock_control_subsys_t sys)
{
	struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
	int ret = -EINVAL;

	if (clk->domain == CPG_MOD) {
		ret = cpg_rmstp_clock_endisable(dev, clk->module, true);
	} else if (clk->domain == CPG_CORE) {
		ret = cpg_core_clock_endisable(dev, clk->module, clk->rate,
					       true);
	}

	return ret;
}

static int cpg_mssr_stop(const struct device *dev,
		     clock_control_subsys_t sys)
{
	struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
	int ret = -EINVAL;

	if (clk->domain == CPG_MOD) {
		ret = cpg_rmstp_clock_endisable(dev, clk->module, false);
	} else if (clk->domain == CPG_CORE) {
		ret = cpg_core_clock_endisable(dev, clk->module, 0, false);
	}

	return ret;
}

static int cpg_get_rate(const struct device *dev,
			clock_control_subsys_t sys,
			uint32_t *rate)
{
	const struct rcar_mssr_config *config = DEV_CFG(dev);
	struct rcar_cpg_clk *clk = (struct rcar_cpg_clk *)sys;
	uint32_t val;
	int ret = 0;

	if (clk->domain != CPG_CORE) {
		return -ENOTSUP;
	}

	switch (clk->module) {
	case CPG_CORE_CLK_CANFD:
		val = sys_read32(config->base_address + CANFDCKCR);
		if (val & CANFDCKCR_CKSTP) {
			*rate = 0;
		} else {
			val &= CANFDCKCR_DIVIDER_MASK;
			*rate = CANFDCKCR_PARENT_CLK_RATE / (val + 1);
		}
		break;
	case CPG_CORE_CLK_S3D4:
		*rate = S3D4_CLK_RATE;
		break;
	default:
		ret = -ENOTSUP;
		break;
	}

	return ret;
}

static int rcar_cpg_mssr_init(const struct device *dev)
{
	return 0;
}

static const struct clock_control_driver_api rcar_cpg_mssr_api = {
	.on = cpg_mssr_blocking_start,
	.off = cpg_mssr_stop,
	.get_rate = cpg_get_rate,
};

#define RCAR_MSSR_INIT(inst)					    \
	static struct rcar_mssr_config rcar_mssr##inst##_config = { \
		.base_address = DT_INST_REG_ADDR(inst)		    \
	};							    \
								    \
	DEVICE_DT_INST_DEFINE(inst,				    \
			      &rcar_cpg_mssr_init,		    \
			      NULL,				    \
			      NULL, &rcar_mssr##inst##_config,	    \
			      PRE_KERNEL_1,			    \
			      CONFIG_KERNEL_INIT_PRIORITY_OBJECTS,  \
			      &rcar_cpg_mssr_api);

DT_INST_FOREACH_STATUS_OKAY(RCAR_MSSR_INIT)