/*
 * Copyright (c) 2021 Sun Amar.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT silabs_gecko_pwm

#include <zephyr/drivers/pwm.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <em_cmu.h>
#include <em_timer.h>

/** PWM configuration. */
struct pwm_gecko_config {
	TIMER_TypeDef *timer;
	CMU_Clock_TypeDef clock;
	uint16_t prescaler;
	TIMER_Prescale_TypeDef prescale_enum;
	uint8_t channel;
	uint8_t location;
	uint8_t port;
	uint8_t pin;
};

static int pwm_gecko_set_cycles(const struct device *dev, uint32_t channel,
				uint32_t period_cycles, uint32_t pulse_cycles,
				pwm_flags_t flags)
{
	TIMER_InitCC_TypeDef compare_config = TIMER_INITCC_DEFAULT;
	const struct pwm_gecko_config *cfg = dev->config;

	if (BUS_RegMaskedRead(&cfg->timer->CC[channel].CTRL,
		_TIMER_CC_CTRL_MODE_MASK) != timerCCModePWM) {

#ifdef _TIMER_ROUTE_MASK
		BUS_RegMaskedWrite(&cfg->timer->ROUTE,
			_TIMER_ROUTE_LOCATION_MASK,
			cfg->location << _TIMER_ROUTE_LOCATION_SHIFT);
		BUS_RegMaskedSet(&cfg->timer->ROUTE, 1 << channel);
#elif defined(_TIMER_ROUTELOC0_MASK)
		BUS_RegMaskedWrite(&cfg->timer->ROUTELOC0,
			_TIMER_ROUTELOC0_CC0LOC_MASK <<
			(channel * _TIMER_ROUTELOC0_CC1LOC_SHIFT),
			cfg->location << (channel * _TIMER_ROUTELOC0_CC1LOC_SHIFT));
		BUS_RegMaskedSet(&cfg->timer->ROUTEPEN, 1 << channel);
#else
#error Unsupported device
#endif

		compare_config.mode = timerCCModePWM;
		TIMER_InitCC(cfg->timer, channel, &compare_config);
	}

	cfg->timer->CC[channel].CTRL |= (flags & PWM_POLARITY_INVERTED) ?
		TIMER_CC_CTRL_OUTINV : 0;

	TIMER_TopSet(cfg->timer, period_cycles);

	TIMER_CompareBufSet(cfg->timer, channel, pulse_cycles);

	return 0;
}

static int pwm_gecko_get_cycles_per_sec(const struct device *dev,
					uint32_t channel, uint64_t *cycles)
{
	const struct pwm_gecko_config *cfg = dev->config;

	*cycles = CMU_ClockFreqGet(cfg->clock) / cfg->prescaler;

	return 0;
}

static const struct pwm_driver_api pwm_gecko_driver_api = {
	.set_cycles = pwm_gecko_set_cycles,
	.get_cycles_per_sec = pwm_gecko_get_cycles_per_sec,
};

static int pwm_gecko_init(const struct device *dev)
{
	TIMER_Init_TypeDef timer = TIMER_INIT_DEFAULT;
	const struct pwm_gecko_config *cfg = dev->config;

	CMU_ClockEnable(cfg->clock, true);

	CMU_ClockEnable(cmuClock_GPIO, true);
	GPIO_PinModeSet(cfg->port, cfg->pin, gpioModePushPull, 0);

	timer.prescale = cfg->prescale_enum;
	TIMER_Init(cfg->timer, &timer);

	return 0;
}

#define CLOCK_TIMER(id) _CONCAT(cmuClock_TIMER, id)
#define PRESCALING_FACTOR(factor) \
	((_CONCAT(timerPrescale, factor)))

#define PWM_GECKO_INIT(index)							\
	static const struct pwm_gecko_config pwm_gecko_config_##index = {	\
		.timer = (TIMER_TypeDef *)DT_REG_ADDR(DT_INST_PARENT(index)),	\
		.clock = CLOCK_TIMER(index),					\
		.prescaler = DT_INST_PROP(index, prescaler),			\
		.prescale_enum = (TIMER_Prescale_TypeDef)			\
			PRESCALING_FACTOR(DT_INST_PROP(index, prescaler)),	\
		.location = DT_INST_PROP_BY_IDX(index, pin_location, 0),	\
		.port = (GPIO_Port_TypeDef)					\
			DT_INST_PROP_BY_IDX(index, pin_location, 1),		\
		.pin = DT_INST_PROP_BY_IDX(index, pin_location, 2),		\
	};									\
										\
	DEVICE_DT_INST_DEFINE(index, &pwm_gecko_init, NULL, NULL,		\
				&pwm_gecko_config_##index, POST_KERNEL,		\
				CONFIG_PWM_INIT_PRIORITY,			\
				&pwm_gecko_driver_api);

DT_INST_FOREACH_STATUS_OKAY(PWM_GECKO_INIT)
