210 lines
5.4 KiB
C
210 lines
5.4 KiB
C
/*
|
|
* Copyright (c) 2020 Henrik Brix Andersen <henrik@brixandersen.dk>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT xlnx_xps_timer_1_00_a_pwm
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/pwm.h>
|
|
#include <zephyr/sys/sys_io.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(xlnx_axi_timer_pwm, CONFIG_PWM_LOG_LEVEL);
|
|
|
|
/* AXI Timer v2.0 registers offsets (See Xilinx PG079 for details) */
|
|
#define TCSR0_OFFSET 0x00
|
|
#define TLR0_OFFSET 0x04
|
|
#define TCR0_OFFSET 0x08
|
|
#define TCSR1_OFFSET 0x10
|
|
#define TLR1_OFFSET 0x14
|
|
#define TCR1_OFFSET 0x18
|
|
|
|
/* TCSRx bit definitions */
|
|
#define TCSR_MDT BIT(0)
|
|
#define TCSR_UDT BIT(1)
|
|
#define TCSR_GENT BIT(2)
|
|
#define TCSR_CAPT BIT(3)
|
|
#define TCSR_ARHT BIT(4)
|
|
#define TCSR_LOAD BIT(5)
|
|
#define TCSR_ENIT BIT(6)
|
|
#define TCSR_ENT BIT(7)
|
|
#define TCSR_TINT BIT(8)
|
|
#define TCSR_PWMA BIT(9)
|
|
#define TCSR_ENALL BIT(10)
|
|
#define TCSR_CASC BIT(11)
|
|
|
|
/* Generate PWM mode, count-down, auto-reload */
|
|
#define TCSR_PWM (TCSR_UDT | TCSR_GENT | TCSR_ARHT | TCSR_PWMA)
|
|
|
|
struct xlnx_axi_timer_config {
|
|
mm_reg_t base;
|
|
uint32_t cycles_max;
|
|
uint32_t freq;
|
|
};
|
|
|
|
static inline uint32_t xlnx_axi_timer_read32(const struct device *dev,
|
|
mm_reg_t offset)
|
|
{
|
|
const struct xlnx_axi_timer_config *config = dev->config;
|
|
|
|
return sys_read32(config->base + offset);
|
|
}
|
|
|
|
static inline void xlnx_axi_timer_write32(const struct device *dev,
|
|
uint32_t value,
|
|
mm_reg_t offset)
|
|
{
|
|
const struct xlnx_axi_timer_config *config = dev->config;
|
|
|
|
sys_write32(value, config->base + offset);
|
|
}
|
|
|
|
static int xlnx_axi_timer_set_cycles(const struct device *dev, uint32_t channel,
|
|
uint32_t period_cycles,
|
|
uint32_t pulse_cycles, pwm_flags_t flags)
|
|
{
|
|
const struct xlnx_axi_timer_config *config = dev->config;
|
|
uint32_t tcsr0 = TCSR_PWM;
|
|
uint32_t tcsr1 = TCSR_PWM;
|
|
uint32_t tlr0;
|
|
uint32_t tlr1;
|
|
|
|
if (channel != 0) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
LOG_DBG("period = 0x%08x, pulse = 0x%08x", period_cycles, pulse_cycles);
|
|
|
|
if (pulse_cycles == 0) {
|
|
LOG_DBG("setting constant inactive level");
|
|
|
|
if (flags & PWM_POLARITY_INVERTED) {
|
|
tcsr0 |= TCSR_ENT;
|
|
} else {
|
|
tcsr1 |= TCSR_ENT;
|
|
}
|
|
} else if (pulse_cycles == period_cycles) {
|
|
LOG_DBG("setting constant active level");
|
|
|
|
if (flags & PWM_POLARITY_INVERTED) {
|
|
tcsr1 |= TCSR_ENT;
|
|
} else {
|
|
tcsr0 |= TCSR_ENT;
|
|
}
|
|
} else {
|
|
LOG_DBG("setting normal pwm");
|
|
|
|
if (period_cycles < 2) {
|
|
LOG_ERR("period cycles too narrow");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* PWM_PERIOD = (TLR0 + 2) * AXI_CLOCK_PERIOD */
|
|
tlr0 = period_cycles - 2;
|
|
|
|
if (tlr0 > config->cycles_max) {
|
|
LOG_ERR("tlr0 out of range (0x%08x > 0x%08x)", tlr0,
|
|
config->cycles_max);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (flags & PWM_POLARITY_INVERTED) {
|
|
/*
|
|
* Since this is a single-channel PWM controller (with
|
|
* no other channels to phase align with) inverse
|
|
* polarity can be achieved simply by inverting the
|
|
* pulse.
|
|
*/
|
|
|
|
if ((period_cycles - pulse_cycles) < 2) {
|
|
LOG_ERR("pulse cycles too narrow");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* PWM_HIGH_TIME = (TLR1 + 2) * AXI_CLOCK_PERIOD */
|
|
tlr1 = period_cycles - pulse_cycles - 2;
|
|
} else {
|
|
if (pulse_cycles < 2) {
|
|
LOG_ERR("pulse cycles too narrow");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* PWM_HIGH_TIME = (TLR1 + 2) * AXI_CLOCK_PERIOD */
|
|
tlr1 = pulse_cycles - 2;
|
|
}
|
|
|
|
LOG_DBG("tlr0 = 0x%08x, tlr1 = 0x%08x", tlr0, tlr1);
|
|
|
|
/* Stop both timers */
|
|
xlnx_axi_timer_write32(dev, TCSR_PWM, TCSR0_OFFSET);
|
|
xlnx_axi_timer_write32(dev, TCSR_PWM, TCSR1_OFFSET);
|
|
|
|
/* Load period cycles */
|
|
xlnx_axi_timer_write32(dev, tlr0, TLR0_OFFSET);
|
|
xlnx_axi_timer_write32(dev, TCSR_PWM | TCSR_LOAD, TCSR0_OFFSET);
|
|
|
|
/* Load pulse cycles */
|
|
xlnx_axi_timer_write32(dev, tlr1, TLR1_OFFSET);
|
|
xlnx_axi_timer_write32(dev, TCSR_PWM | TCSR_LOAD, TCSR1_OFFSET);
|
|
|
|
/* Start both timers */
|
|
tcsr1 |= TCSR_ENALL;
|
|
}
|
|
|
|
xlnx_axi_timer_write32(dev, tcsr0, TCSR0_OFFSET);
|
|
xlnx_axi_timer_write32(dev, tcsr1, TCSR1_OFFSET);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xlnx_axi_timer_get_cycles_per_sec(const struct device *dev,
|
|
uint32_t channel, uint64_t *cycles)
|
|
{
|
|
const struct xlnx_axi_timer_config *config = dev->config;
|
|
|
|
ARG_UNUSED(channel);
|
|
|
|
*cycles = config->freq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xlnx_axi_timer_init(const struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct pwm_driver_api xlnx_axi_timer_driver_api = {
|
|
.set_cycles = xlnx_axi_timer_set_cycles,
|
|
.get_cycles_per_sec = xlnx_axi_timer_get_cycles_per_sec,
|
|
};
|
|
|
|
#define XLNX_AXI_TIMER_ASSERT_PROP_VAL(n, prop, val, str) \
|
|
BUILD_ASSERT(DT_INST_PROP(n, prop) == val, str)
|
|
|
|
#define XLNX_AXI_TIMER_INIT(n) \
|
|
XLNX_AXI_TIMER_ASSERT_PROP_VAL(n, xlnx_gen0_assert, 1, \
|
|
"xlnx,gen0-assert must be 1 for pwm"); \
|
|
XLNX_AXI_TIMER_ASSERT_PROP_VAL(n, xlnx_gen1_assert, 1, \
|
|
"xlnx,gen1-assert must be 1 for pwm"); \
|
|
XLNX_AXI_TIMER_ASSERT_PROP_VAL(n, xlnx_one_timer_only, 0, \
|
|
"xlnx,one-timer-only must be 0 for pwm"); \
|
|
\
|
|
static struct xlnx_axi_timer_config xlnx_axi_timer_config_##n = { \
|
|
.base = DT_INST_REG_ADDR(n), \
|
|
.freq = DT_INST_PROP(n, clock_frequency), \
|
|
.cycles_max = \
|
|
GENMASK(DT_INST_PROP(n, xlnx_count_width) - 1, 0), \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, &xlnx_axi_timer_init, \
|
|
NULL, NULL, \
|
|
&xlnx_axi_timer_config_##n, \
|
|
POST_KERNEL, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
|
&xlnx_axi_timer_driver_api)
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(XLNX_AXI_TIMER_INIT);
|