You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
5.4 KiB
213 lines
5.4 KiB
/* |
|
* 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 <device.h> |
|
#include <drivers/pwm.h> |
|
#include <sys/sys_io.h> |
|
#include <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_pin_set(const struct device *dev, uint32_t pwm, |
|
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 (pwm != 0) { |
|
return -ENOTSUP; |
|
} |
|
|
|
if (pulse_cycles > period_cycles) { |
|
LOG_ERR("pulse cycles must be less than or equal to period"); |
|
return -EINVAL; |
|
} |
|
|
|
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 pwm, uint64_t *cycles) |
|
{ |
|
const struct xlnx_axi_timer_config *config = dev->config; |
|
|
|
ARG_UNUSED(pwm); |
|
|
|
*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 = { |
|
.pin_set = xlnx_axi_timer_pin_set, |
|
.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);
|
|
|