diff --git a/arch/risc-v/Kconfig b/arch/risc-v/Kconfig index 87ee85d2898..ff7e4285149 100644 --- a/arch/risc-v/Kconfig +++ b/arch/risc-v/Kconfig @@ -66,6 +66,7 @@ config ARCH_CHIP_MPFS select ARCH_RV64GC select ARCH_HAVE_MPU select ARCH_HAVE_RESET + select ARCH_HAVE_PWM_MULTICHAN ---help--- MicroChip Polarfire processor (RISC-V 64bit core with GCVX extensions). diff --git a/arch/risc-v/src/mpfs/Kconfig b/arch/risc-v/src/mpfs/Kconfig index dc8ced74850..287db4ddd57 100755 --- a/arch/risc-v/src/mpfs/Kconfig +++ b/arch/risc-v/src/mpfs/Kconfig @@ -103,6 +103,70 @@ config MPFS_I2C1 select ARCH_HAVE_I2CRESET default n +comment "CorePWM Options" + +config MPFS_HAVE_COREPWM + bool "CorePWM FPGA IP block configured" + default n + +config MPFS_COREPWM0 + bool "CorePWM0 FPGA IP block configured" + default n + select PWM_MULTICHAN + depends on MPFS_HAVE_COREPWM + +config MPFS_COREPWM0_BASE + hex "Base address for the instance" + default 0x44000000 + depends on MPFS_COREPWM0 + +config MPFS_COREPWM0_PWMCLK + int "Clock frequency of the CorePWM0 block (Hz)" + default 25000000 + range 1000000 100000000 + depends on MPFS_COREPWM0 + +config MPFS_COREPWM0_REGWIDTH + int "Width of the PWM register (8, 16 or 32 bits)" + default 32 + range 8 32 + depends on MPFS_COREPWM0 + +config MPFS_COREPWM0_NCHANNELS + int "Number of Output Channels for CorePWM0" + default 8 + range 1 16 + depends on MPFS_COREPWM0 + +config MPFS_COREPWM1 + bool "CorePWM1 FPGA IP block configured" + default n + select PWM_MULTICHAN + depends on MPFS_HAVE_COREPWM + +config MPFS_COREPWM1_BASE + hex "Base address for the instance" + default 0x45000000 + depends on MPFS_COREPWM1 + +config MPFS_COREPWM1_PWMCLK + int "Clock frequency of the CorePWM1 block (Hz)" + default 25000000 + range 1000000 100000000 + depends on MPFS_COREPWM1 + +config MPFS_COREPWM1_REGWIDTH + int "Width of the PWM register (8, 16 or 32 bits)" + default 32 + range 8 32 + depends on MPFS_COREPWM1 + +config MPFS_COREPWM1_NCHANNELS + int "Number of Output Channels for CorePWM1" + default 2 + range 1 16 + depends on MPFS_COREPWM1 + endmenu config MPFS_DMA diff --git a/arch/risc-v/src/mpfs/Make.defs b/arch/risc-v/src/mpfs/Make.defs index 35bbc0019c7..69f5effdb89 100755 --- a/arch/risc-v/src/mpfs/Make.defs +++ b/arch/risc-v/src/mpfs/Make.defs @@ -73,3 +73,7 @@ endif ifeq ($(CONFIG_I2C),y) CHIP_CSRCS += mpfs_i2c.c endif + +ifeq (${CONFIG_MPFS_HAVE_COREPWM},y) +CHIP_CSRCS += mpfs_corepwm.c +endif diff --git a/arch/risc-v/src/mpfs/hardware/mpfs_corepwm.h b/arch/risc-v/src/mpfs/hardware/mpfs_corepwm.h new file mode 100644 index 00000000000..ea938bc6199 --- /dev/null +++ b/arch/risc-v/src/mpfs/hardware/mpfs_corepwm.h @@ -0,0 +1,98 @@ +/**************************************************************************** + * arch/risc-v/src/mpfs/hardware/mpfs_corepwm.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_RISCV_SRC_MPFS_HARDWARE_MPFS_COREPWM_H +#define __ARCH_RISCV_SRC_MPFS_HARDWARE_MPFS_COREPWM_H + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* CorePWM features *********************************************************/ + +#define MPFS_MAX_PWM_CHANNELS 16 + +/* Register Base Address ****************************************************/ + +#define MPFS_COREPWM0_BASE (CONFIG_MPFS_COREPWM0_BASE) +#define MPFS_COREPWM1_BASE (CONFIG_MPFS_COREPWM1_BASE) + +/* Register offsets *********************************************************/ + +#define MPFS_COREPWM_PRESCALE_OFFSET (0x00) +#define MPFS_COREPWM_PERIOD_OFFSET (0x04) +#define MPFS_COREPWM_PWM_ENABLE_0_7_OFFSET (0x08) +#define MPFS_COREPWM_PWM_ENABLE_8_15_OFFSET (0x0C) +#define MPFS_COREPWM_PWM1_POS_EDGE_OFFSET (0x10) +#define MPFS_COREPWM_PWM1_NEG_EDGE_OFFSET (0x14) +#define MPFS_COREPWM_PWM2_POS_EDGE_OFFSET (0x18) +#define MPFS_COREPWM_PWM2_NEG_EDGE_OFFSET (0x1C) +#define MPFS_COREPWM_PWM3_POS_EDGE_OFFSET (0x20) +#define MPFS_COREPWM_PWM3_NEG_EDGE_OFFSET (0x24) +#define MPFS_COREPWM_PWM4_POS_EDGE_OFFSET (0x28) +#define MPFS_COREPWM_PWM4_NEG_EDGE_OFFSET (0x2C) +#define MPFS_COREPWM_PWM5_POS_EDGE_OFFSET (0x30) +#define MPFS_COREPWM_PWM5_NEG_EDGE_OFFSET (0x34) +#define MPFS_COREPWM_PWM6_POS_EDGE_OFFSET (0x38) +#define MPFS_COREPWM_PWM6_NEG_EDGE_OFFSET (0x3C) +#define MPFS_COREPWM_PWM7_POS_EDGE_OFFSET (0x40) +#define MPFS_COREPWM_PWM7_NEG_EDGE_OFFSET (0x44) +#define MPFS_COREPWM_PWM8_POS_EDGE_OFFSET (0x48) +#define MPFS_COREPWM_PWM8_NEG_EDGE_OFFSET (0x4C) +#define MPFS_COREPWM_PWM9_POS_EDGE_OFFSET (0x50) +#define MPFS_COREPWM_PWM9_NEG_EDGE_OFFSET (0x54) +#define MPFS_COREPWM_PWM10_POS_EDGE_OFFSET (0x58) +#define MPFS_COREPWM_PWM10_NEG_EDGE_OFFSET (0x5C) +#define MPFS_COREPWM_PWM11_POS_EDGE_OFFSET (0x60) +#define MPFS_COREPWM_PWM11_NEG_EDGE_OFFSET (0x64) +#define MPFS_COREPWM_PWM12_POS_EDGE_OFFSET (0x68) +#define MPFS_COREPWM_PWM12_NEG_EDGE_OFFSET (0x6C) +#define MPFS_COREPWM_PWM13_POS_EDGE_OFFSET (0x70) +#define MPFS_COREPWM_PWM13_NEG_EDGE_OFFSET (0x74) +#define MPFS_COREPWM_PWM14_POS_EDGE_OFFSET (0x78) +#define MPFS_COREPWM_PWM14_NEG_EDGE_OFFSET (0x7C) +#define MPFS_COREPWM_PWM15_POS_EDGE_OFFSET (0x80) +#define MPFS_COREPWM_PWM15_NEG_EDGE_OFFSET (0x84) +#define MPFS_COREPWM_PWM16_POS_EDGE_OFFSET (0x88) +#define MPFS_COREPWM_PWM16_NEG_EDGE_OFFSET (0x8C) +#define MPFS_COREPWM_STRETCH_OFFSET (0x90) +#define MPFS_COREPWM_TACHPRESCALE_OFFSET (0x94) +#define MPFS_COREPWM_TACHSTATUS_OFFSET (0x98) +#define MPFS_COREPWM_TACHIRQMASK_OFFSET (0x9C) +#define MPFS_COREPWM_TACHMODE_OFFSET (0xA0) +#define MPFS_COREPWM_TACHPULSEDUR_0_OFFSET (0xA4) +#define MPFS_COREPWM_TACHPULSEDUR_1_OFFSET (0xA8) +#define MPFS_COREPWM_TACHPULSEDUR_2_OFFSET (0xAC) +#define MPFS_COREPWM_TACHPULSEDUR_3_OFFSET (0xB0) +#define MPFS_COREPWM_TACHPULSEDUR_4_OFFSET (0xB4) +#define MPFS_COREPWM_TACHPULSEDUR_5_OFFSET (0xB8) +#define MPFS_COREPWM_TACHPULSEDUR_6_OFFSET (0xBC) +#define MPFS_COREPWM_TACHPULSEDUR_7_OFFSET (0xC0) +#define MPFS_COREPWM_TACHPULSEDUR_8_OFFSET (0xC4) +#define MPFS_COREPWM_TACHPULSEDUR_9_OFFSET (0xC8) +#define MPFS_COREPWM_TACHPULSEDUR_10_OFFSET (0xCC) +#define MPFS_COREPWM_TACHPULSEDUR_11_OFFSET (0xD0) +#define MPFS_COREPWM_TACHPULSEDUR_12_OFFSET (0xD4) +#define MPFS_COREPWM_TACHPULSEDUR_13_OFFSET (0xD8) +#define MPFS_COREPWM_TACHPULSEDUR_14_OFFSET (0xDC) +#define MPFS_COREPWM_TACHPULSEDUR_15_OFFSET (0xE0) +#define MPFS_COREPWM_SYNC_UPDATE_OFFSET (0xE4) + +#endif /* __ARCH_RISCV_SRC_MPFS_HARDWARE_MPFS_COREPWM_H */ diff --git a/arch/risc-v/src/mpfs/mpfs_corepwm.c b/arch/risc-v/src/mpfs/mpfs_corepwm.c new file mode 100644 index 00000000000..203ba484129 --- /dev/null +++ b/arch/risc-v/src/mpfs/mpfs_corepwm.c @@ -0,0 +1,787 @@ +/**************************************************************************** + * arch/risc-v/src/mpfs/mpfs_corepwm.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "hardware/mpfs_corepwm.h" + +#include "riscv_arch.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef OK +# define OK 0 +#endif + +/* This module only compiles if at least one CorePWM instance + * is configured to the FPGA + */ + +#ifndef CONFIG_MPFS_HAVE_COREPWM +# error This should not be compiled as CorePWM block is not defined/configured +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct mpfs_pwmchan_s +{ + uint8_t channel; /* Timer output channel: {1,..16} */ +}; + +/* This structure represents the state of one PWM timer */ + +struct mpfs_pwmtimer_s +{ + FAR const struct pwm_ops_s *ops; /* PWM operations */ + uint8_t nchannels; /* Number of channels on this PWM block */ + uint8_t pwmid; /* PWM ID {1,...} */ + struct mpfs_pwmchan_s channels[MPFS_MAX_PWM_CHANNELS]; + uint32_t frequency; /* Current frequency setting */ + uintptr_t base; /* The base address of the pwm block */ + uint32_t pwmclk; /* The frequency of the pwm clock */ +}; + +/**************************************************************************** + * Static Function Prototypes + ****************************************************************************/ + +/* Register access */ + +static uint32_t pwm_getreg(struct mpfs_pwmtimer_s *priv, int offset); +static void pwm_putreg(struct mpfs_pwmtimer_s *priv, int offset, + uint32_t value); + +#ifdef CONFIG_DEBUG_PWM_INFO +static void pwm_dumpregs(struct mpfs_pwmtimer_s *priv, FAR const char *msg); +#else +# define pwm_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int pwm_timer(FAR struct mpfs_pwmtimer_s *priv, + FAR const struct pwm_info_s *info); + +/* PWM driver methods */ + +static int pwm_setup(FAR struct pwm_lowerhalf_s *dev); +static int pwm_shutdown(FAR struct pwm_lowerhalf_s *dev); + +static int pwm_start(FAR struct pwm_lowerhalf_s *dev, + FAR const struct pwm_info_s *info); + +static int pwm_stop(FAR struct pwm_lowerhalf_s *dev); +static int pwm_ioctl(FAR struct pwm_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This is the list of lower half PWM driver methods used by the upper half + * driver + */ + +static const struct pwm_ops_s g_pwmops = +{ + .setup = pwm_setup, + .shutdown = pwm_shutdown, + .start = pwm_start, + .stop = pwm_stop, + .ioctl = pwm_ioctl, +}; + +#ifdef CONFIG_MPFS_COREPWM0 +static struct mpfs_pwmtimer_s g_pwm0dev = +{ + .ops = &g_pwmops, + .nchannels = CONFIG_MPFS_COREPWM0_NCHANNELS, + .pwmid = 0, + .channels = + { + { + .channel = 1 + }, + { + .channel = 2 + }, + { + .channel = 3 + }, + { + .channel = 4 + }, + { + .channel = 5 + }, + { + .channel = 6 + }, + { + .channel = 7 + }, + { + .channel = 8 + }, + { + .channel = 9 + }, + { + .channel = 10 + }, + { + .channel = 11 + }, + { + .channel = 12 + }, + { + .channel = 13 + }, + { + .channel = 14 + }, + { + .channel = 15 + }, + { + .channel = 16 + } + }, + .base = CONFIG_MPFS_COREPWM0_BASE, + .pwmclk = CONFIG_MPFS_COREPWM0_PWMCLK, +}; +#endif + +#ifdef CONFIG_MPFS_COREPWM1 +static struct mpfs_pwmtimer_s g_pwm1dev = +{ + .ops = &g_pwmops, + .nchannels = CONFIG_MPFS_COREPWM1_NCHANNELS, + .pwmid = 1, + .channels = + { + { + .channel = 1 + }, + { + .channel = 2 + }, + { + .channel = 3 + }, + { + .channel = 4 + }, + { + .channel = 5 + }, + { + .channel = 6 + }, + { + .channel = 7 + }, + { + .channel = 8 + }, + { + .channel = 9 + }, + { + .channel = 10 + }, + { + .channel = 11 + }, + { + .channel = 12 + }, + { + .channel = 13 + }, + { + .channel = 14 + }, + { + .channel = 15 + }, + { + .channel = 16 + } + }, + .base = CONFIG_MPFS_COREPWM1_BASE, + .pwmclk = CONFIG_MPFS_COREPWM1_PWMCLK, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pwm_getreg + * + * Description: + * Read the value of an PWM timer register. + * + * Input Parameters: + * priv - A reference to the PWM block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t pwm_getreg(struct mpfs_pwmtimer_s *priv, int offset) +{ + return getreg32(priv->base + offset); +} + +/**************************************************************************** + * Name: pwm_putreg + * + * Description: + * Read the value of an PWM timer register. + * + * Input Parameters: + * priv - A reference to the PWM block status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pwm_putreg(struct mpfs_pwmtimer_s *priv, int offset, + uint32_t value) +{ + /* TODO: 8,16 & 32 bit reg width consideration + * 32 bit access is required for a 32 bit register + */ + + putreg32(value, priv->base + offset); +} + +/**************************************************************************** + * Name: pwm_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input Parameters: + * priv - A reference to the PWM block status + * + * Returned Value: + * None + * + * TODO: Add TACH* register if tachometer feature is taken in use + * TODO: Add DAC* register if DA feature is taken in use + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_PWM_INFO +#define MPFS_PWMREG_STEP (MPFS_COREPWM_PWM2_POS_EDGE_OFFSET - MPFS_COREPWM_PWM1_POS_EDGE_OFFSET) + +static void pwm_dumpregs(struct mpfs_pwmtimer_s *priv, FAR const char *msg) +{ + pwminfo("%s:\n", msg); + pwminfo(" PRESCALE: %08x PERIOD: %08x\n", + pwm_getreg(priv, MPFS_COREPWM_PRESCALE_OFFSET), + pwm_getreg(priv, MPFS_COREPWM_PERIOD_OFFSET)); + pwminfo(" SYNC_UPDATE: %02x\n", + pwm_getreg(priv, MPFS_COREPWM_SYNC_UPDATE_OFFSET)); + pwminfo(" PWM_ENABLE_0_7: %02x PWM_ENABLE_8_15: %02x\n", + pwm_getreg(priv, MPFS_COREPWM_PWM_ENABLE_0_7_OFFSET), + pwm_getreg(priv, MPFS_COREPWM_PWM_ENABLE_8_15_OFFSET)); + + for (int i = 0; i < priv->nchannels; i++) + { + pwminfo(" PWM%d_POSEDGE: %s%08x PWM%d_NEGEDGE: %s%08x\n", + i + 1, (i < 9) ? " " : "", + pwm_getreg(priv, MPFS_COREPWM_PWM1_POS_EDGE_OFFSET + + i * MPFS_PWMREG_STEP), + i + 1, (i < 9) ? " " : "", + pwm_getreg(priv, MPFS_COREPWM_PWM1_NEG_EDGE_OFFSET + + i * MPFS_PWMREG_STEP)); + } +} +#endif + +/**************************************************************************** + * Name: pwm_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * priv - A reference to the lower half PWM driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_timer(FAR struct mpfs_pwmtimer_s *priv, + FAR const struct pwm_info_s *info) +{ + int i; + + /* Calculated values */ + + /* TODO: We might need to calculate prescaler on some rare cases, + * for now hardcoded to 0 + */ + + uint32_t prescaler = 0; + + uint32_t period; + + DEBUGASSERT(priv != NULL && info != NULL); + DEBUGASSERT(info->frequency > 0); + + /* CorePWM FPGA block can be configured to be with either 8, 16, or 32 bit + * registers width. Minimally PWM functionality is set up by two registers: + * PRESCALE and PERIOD which are common to all channels. Up to 16 channels + * may be configured in use. Clock used by the block may be selected in + * design phase an on Icicle Kit reference design version 21.04 has at + * least the following clock signals to choose from the Clocks_and_Resets + * block: 125MHz, 100MHz, 75MHz, 62.5MHz, 50MHz, and 25MHz. + * + * For now only 32 the bit configuration is supported. + * TODO: Add 8 and 16 bit width support + * + * There are many combinations of prescaler and period registers, but the + * best will be the one that has the smallest prescaler value. That is the + * solution that should give us the most accuracy in the pwm control. + * + * Example for clk = 25MHz, prescale 0 and 32 bit wide registers: + * PWM period granularity PWM_PG = (PRESCALE + 1) / pwmclk = + * 40 ns × 1 = 40 ns, so the smallest step is 40ns + * pwmclk = clk / (PRESCALE + 1) = 25,000,000 / (PRESCALE + 1) = + * 25,000,000 + * + * For desired output frequency of 50Hz and using PRESCALE of 0: + * PERIOD = pwmclk / frequency = 25,000,000 / 50 = 500,000 + */ + + pwminfo("PWM%u frequency: %u PWMCLK: %u prescaler: %u\n", + priv->pwmid, info->frequency, priv->pwmclk, prescaler); + + /* Set the reload and prescaler values */ + + period = priv->pwmclk / info->frequency; + + pwm_putreg(priv, MPFS_COREPWM_PERIOD_OFFSET, period); + pwm_putreg(priv, MPFS_COREPWM_PRESCALE_OFFSET, prescaler); + + /* Handle channel specific setup */ + + for (i = 0; i < CONFIG_PWM_NCHANNELS; i++) + { + ub32_t duty; + uint8_t channel; + uint32_t neg_edge; + + channel = info->channels[i].channel; + + /* Duty defined as fraction of 65536, i.e. a value of 1 to 65535 + * corresponding to a duty cycle of 0.000015 - 0.999984 + */ + + duty = ub16toub32(info->channels[i].duty); + neg_edge = b32toi(duty * period + b32HALF); + + if (channel == 0) /* A value of zero means to skip this channel */ + { + continue; + } + + if (channel > MPFS_MAX_PWM_CHANNELS) + { + pwmerr("ERROR: No such PWM channel: %u\n", channel); + return -EINVAL; + } + + /* Set the channels duty cycle by writing to the NEG_EDGE register + * for this channel + */ + + const int neg_edge_reg_offset = + MPFS_COREPWM_PWM1_NEG_EDGE_OFFSET + + (MPFS_COREPWM_PWM2_NEG_EDGE_OFFSET - + MPFS_COREPWM_PWM1_NEG_EDGE_OFFSET) * (channel - 1); + + pwm_putreg(priv, neg_edge_reg_offset, neg_edge); + + /* Enable the channel */ + + if (channel <= 8) + { + uint32_t reg = pwm_getreg(priv, + MPFS_COREPWM_PWM_ENABLE_0_7_OFFSET); + pwm_putreg(priv, MPFS_COREPWM_PWM_ENABLE_0_7_OFFSET, + reg | (1 << (channel - 1))); + } + else + { + uint32_t reg = pwm_getreg(priv, + MPFS_COREPWM_PWM_ENABLE_8_15_OFFSET); + pwm_putreg(priv, MPFS_COREPWM_PWM_ENABLE_8_15_OFFSET, + reg | (1 << (channel - 9))); + } + } + + pwm_dumpregs(priv, "After starting"); + + return OK; +} + +/**************************************************************************** + * Name: pwm_update_duty + * + * Description: + * Change the channel duty cycle. + * + * Input Parameters: + * priv - A reference to the lower half PWM driver state structure + * channel - Channel to by updated + * duty - New duty cycle + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_update_duty(FAR struct mpfs_pwmtimer_s *priv, + uint8_t channel, ub16_t duty16) +{ + uint32_t period; + uint32_t neg_edge; + ub32_t duty = ub16toub32(duty16); + + DEBUGASSERT(priv != NULL); + + if (channel == 0 || channel > priv->nchannels || + channel > MPFS_MAX_PWM_CHANNELS) + { + pwmerr("ERROR: PWM%d has no such channel: %u\n", priv->pwmid, channel); + return -EINVAL; + } + + pwminfo("PWM%u channel %u, duty %08x\n", priv->pwmid, channel, duty16); + + period = pwm_getreg(priv, MPFS_COREPWM_PERIOD_OFFSET); + neg_edge = b32toi(duty * period + b32HALF); + + /* Set the channels duty cycle by writing to the NEG_EDGE register + * for this channel + */ + + const int neg_edge_reg_offset = + MPFS_COREPWM_PWM1_NEG_EDGE_OFFSET + + (MPFS_COREPWM_PWM2_NEG_EDGE_OFFSET - + MPFS_COREPWM_PWM1_NEG_EDGE_OFFSET) * (channel - 1); + + pwm_putreg(priv, neg_edge_reg_offset, neg_edge); + + return OK; +} + +/**************************************************************************** + * Name: pwm_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * + * Note: + * On a MPFS CorePWM block no setting up is needed + * + ****************************************************************************/ + +static int pwm_setup(FAR struct pwm_lowerhalf_s *dev) +{ + FAR struct mpfs_pwmtimer_s *priv = (FAR struct mpfs_pwmtimer_s *)dev; + + pwminfo("PWMID%u\n", priv->pwmid); + pwm_dumpregs(priv, "Initially"); + + return OK; +} + +/**************************************************************************** + * Name: pwm_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_shutdown(FAR struct pwm_lowerhalf_s *dev) +{ + FAR struct mpfs_pwmtimer_s *priv = (FAR struct mpfs_pwmtimer_s *)dev; + + pwminfo("PWM%u\n", priv->pwmid); + + /* Make sure that the output has been stopped */ + + pwm_stop(dev); + + return OK; +} + +/**************************************************************************** + * Name: pwm_start + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_start(FAR struct pwm_lowerhalf_s *dev, + FAR const struct pwm_info_s *info) +{ + int ret = OK; + FAR struct mpfs_pwmtimer_s *priv = (FAR struct mpfs_pwmtimer_s *)dev; + + /* if frequency has not changed we just update duty */ + + if (info->frequency == priv->frequency) + { + int i; + + pwminfo("PWM%u, no change in frequency\n", priv->pwmid); + + for (i = 0; + ret == OK && i < MPFS_MAX_PWM_CHANNELS && i < priv->nchannels; + i++) + { + /* Set output if channel configured */ + + uint8_t chan = info->channels[i].channel; + + if (chan != 0 && chan <= priv->nchannels) + { + pwminfo(" channel %d, duty %d\n", chan, + info->channels[i].duty); + ret = pwm_update_duty(priv, chan, info->channels[i].duty); + } + } + } + else + { + pwminfo("PWM%u, change frequency and duty cycle\n", priv->pwmid); + + ret = pwm_timer(priv, info); + + /* Save current frequency */ + + if (ret == OK) + { + priv->frequency = info->frequency; + } + } + + return ret; +} + +/**************************************************************************** + * Name: pwm_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. + * + ****************************************************************************/ + +static int pwm_stop(FAR struct pwm_lowerhalf_s *dev) +{ + FAR struct mpfs_pwmtimer_s *priv = (FAR struct mpfs_pwmtimer_s *)dev; + + pwminfo("PWM%u pwm_stop\n", priv->pwmid); + + /* Check that timer number is valid */ + + switch (priv->pwmid) + { +#ifdef CONFIG_MPFS_COREPWM0 + case 0: + break; +#endif +#ifdef CONFIG_MPFS_COREPWM1 + case 1: + break; +#endif + default: + return -EINVAL; + } + + /* Stopped so set frequency to zero */ + + priv->frequency = 0; + + /* No resetting on CorePWM block so just disable the channels and + * it is in a state where pwm_start() can be called. + */ + + pwm_putreg(priv, MPFS_COREPWM_PWM_ENABLE_0_7_OFFSET, 0x00); + pwm_putreg(priv, MPFS_COREPWM_PWM_ENABLE_8_15_OFFSET, 0x00); + + pwm_dumpregs(priv, "After stop"); + + return OK; +} + +/**************************************************************************** + * Name: pwm_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half PWM driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pwm_ioctl(FAR struct pwm_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_PWM_INFO + FAR struct mpfs_pwmtimer_s *priv = (FAR struct mpfs_pwmtimer_s *)dev; + + /* There are no platform-specific ioctl commands */ + + pwminfo("PWM%u\n", priv->pwmid); +#endif + + return -ENOTTY; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mpfs_corepwm_init + * + * Description: + * Initialize a CorePWM block. + * + * Input Parameters: + * pwmid - A number identifying the pwm block. The number of valid + * IDs varies depending on the configuration of the FPGA. + * + * Returned Value: + * On success, a pointer to the MPFS CorePWM lower half PWM driver is + * returned. NULL is returned on any failure. + * + ****************************************************************************/ + +FAR struct pwm_lowerhalf_s *mpfs_corepwm_init(int pwmid) +{ + FAR struct mpfs_pwmtimer_s *lower; + + pwminfo("PWM%u\n", pwmid); + + switch (pwmid) + { +#ifdef CONFIG_MPFS_COREPWM0 + case 0: + lower = &g_pwm0dev; + break; +#endif +#ifdef CONFIG_MPFS_COREPWM1 + case 1: + lower = &g_pwm1dev; + break; +#endif + default: + pwmerr("ERROR: No such timer configured\n"); + return NULL; + } + + return (FAR struct pwm_lowerhalf_s *)lower; +} + diff --git a/arch/risc-v/src/mpfs/mpfs_corepwm.h b/arch/risc-v/src/mpfs/mpfs_corepwm.h new file mode 100644 index 00000000000..7e83476ee11 --- /dev/null +++ b/arch/risc-v/src/mpfs/mpfs_corepwm.h @@ -0,0 +1,100 @@ +/**************************************************************************** + * arch/risc-v/src/mpfs/mpfs_corepwm.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_RISCV_SRCMPFS_MPFS_MPFS_COREPWM_H +#define __ARCH_RISCV_SRCMPFS_MPFS_MPFS_COREPWM_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Check if PWM support for any channel is enabled. */ + +#ifdef CONFIG_MPFS_HAVE_COREPWM + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include "mpfs_hal/mss_hal.h" +#include "hardware/mpfs_corepwm.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: mpfs_corepwm_init + * + * Description: + * Initialize a CorePWM block. + * + * Input Parameters: + * pwmid - A number identifying the pwm block. The number of valid + * IDs varies depending on the configuration of the FPGA. + * + * Returned Value: + * On success, a pointer to the MPFS CorePWM lower half PWM driver is + * returned. NULL is returned on any failure. + * + ****************************************************************************/ + +FAR struct pwm_lowerhalf_s *mpfs_corepwm_init(int pwmid); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* CONFIG_MPFS_HAVE_COREPWM */ +#endif /* __ARCH_RISCV_SRCMPFS_MPFS_MPFS_COREPWM_H */ diff --git a/boards/risc-v/mpfs/icicle/src/Makefile b/boards/risc-v/mpfs/icicle/src/Makefile index 8dc195e8b4c..e493ec84921 100755 --- a/boards/risc-v/mpfs/icicle/src/Makefile +++ b/boards/risc-v/mpfs/icicle/src/Makefile @@ -42,4 +42,8 @@ ifeq ($(CONFIG_SPI),y) CSRCS += mpfs_board_spi.c endif +ifeq ($(CONFIG_MPFS_HAVE_COREPWM),y) +CSRCS += mpfs_pwm.c +endif + include $(TOPDIR)/boards/Board.mk diff --git a/boards/risc-v/mpfs/icicle/src/mpfs_bringup.c b/boards/risc-v/mpfs/icicle/src/mpfs_bringup.c index 983ba9489e3..f781afe74b8 100755 --- a/boards/risc-v/mpfs/icicle/src/mpfs_bringup.c +++ b/boards/risc-v/mpfs/icicle/src/mpfs_bringup.c @@ -34,6 +34,7 @@ #include #include "mpfsicicle.h" +#include "mpfs_corepwm.h" #include "mpfs.h" /**************************************************************************** @@ -80,5 +81,16 @@ int mpfs_bringup(void) } #endif +#ifdef CONFIG_MPFS_HAVE_COREPWM + /* Configure PWM peripheral interfaces */ + + ret = mpfs_pwm_setup(); + + if (ret < 0) + { + syslog(LOG_ERR, "Failed to initialize CorePWM driver: %d\n", ret); + } +#endif + return ret; } diff --git a/boards/risc-v/mpfs/icicle/src/mpfs_pwm.c b/boards/risc-v/mpfs/icicle/src/mpfs_pwm.c new file mode 100644 index 00000000000..823a58a7ac1 --- /dev/null +++ b/boards/risc-v/mpfs/icicle/src/mpfs_pwm.c @@ -0,0 +1,90 @@ +/**************************************************************************** + * boards/risc-v/mpfs/icicle/src/mpfs_pwm.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include "mpfs_corepwm.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mpfs_pwm_setup + * + * Description: + * + * Initialize PWM and register PWM devices + * + ****************************************************************************/ + +int mpfs_pwm_setup(void) +{ + struct pwm_lowerhalf_s *lower_half = NULL; /* lower-half handle */ + + /* The underlying CorePWM driver "knows" there are up to 16 channels + * available for each timer device, so we don't have to do anything + * special here. + */ + +#ifdef CONFIG_MPFS_COREPWM0 + lower_half = mpfs_corepwm_init(0); + + /* If we can't get the lower-half handle, skip and keep going. */ + + if (lower_half) + { + /* Translate the peripheral number to a device name. */ + + pwm_register("/dev/corepwm0", lower_half); + } + +#endif +#ifdef CONFIG_MPFS_COREPWM1 + lower_half = mpfs_corepwm_init(1); + + /* If we can't get the lower-half handle, skip and keep going. */ + + if (lower_half) + { + /* Translate the peripheral number to a device name. */ + + pwm_register("/dev/corepwm1", lower_half); + } +#endif + + return 0; +} diff --git a/boards/risc-v/mpfs/icicle/src/mpfsicicle.h b/boards/risc-v/mpfs/icicle/src/mpfsicicle.h index 2d00e3acb63..600e7f936d5 100755 --- a/boards/risc-v/mpfs/icicle/src/mpfsicicle.h +++ b/boards/risc-v/mpfs/icicle/src/mpfsicicle.h @@ -44,5 +44,6 @@ int mpfs_bringup(void); int mpfs_board_spi_init(void); int mpfs_board_i2c_init(void); +int mpfs_pwm_setup(void); #endif /* __BOARDS_RISCV_ICICLE_MPFS_SRC_MPFSICICLE_H */