blob: 197cd7279877584b59257de9294c683f575ff054 [file] [log] [blame]
/*
* 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.
*/
#include <assert.h>
#include <string.h>
#include <errno.h>
#include "os/mynewt.h"
#include <bsp.h>
#include <hal/hal_bsp.h>
#include <pwm/pwm.h>
#include <mcu/cmsis_nvic.h>
/* Nordic headers */
#include <nrfx.h>
#include <nrfx_pwm.h>
#include <nrf_pwm.h>
/* Mynewt Nordic driver */
#include "pwm_nrf52/pwm_nrf52.h"
/* Max number on PWM instances on existing nRF52xxx MCUs */
#define NRF52_PWM_MAX_INSTANCES 4
struct nrf52_pwm_dev_global {
bool in_use;
bool playing;
nrfx_pwm_t drv_instance;
nrfx_pwm_config_t config;
nrf_pwm_values_individual_t duty_cycles;
uint32_t n_cycles;
nrfx_pwm_flag_t flags;
nrfx_pwm_handler_t internal_handler;
user_handler_t cycle_handler;
user_handler_t seq_end_handler;
void* cycle_data;
void* seq_end_data;
};
static struct nrf52_pwm_dev_global instances[] =
{
#if MYNEWT_VAL(PWM_0)
[0].in_use = false,
[0].playing = false,
[0].drv_instance = NRFX_PWM_INSTANCE(0),
[0].config = NRFX_PWM_DEFAULT_CONFIG,
[0].flags = NRFX_PWM_FLAG_LOOP,
[0].duty_cycles = {0},
[0].n_cycles = 1,
[0].internal_handler = NULL,
[0].cycle_handler = NULL,
[0].cycle_data = NULL,
[0].seq_end_handler = NULL,
[0].seq_end_data = NULL
#endif
#if MYNEWT_VAL(PWM_1)
,
[1].in_use = false,
[1].playing = false,
[1].drv_instance = NRFX_PWM_INSTANCE(1),
[1].config = NRFX_PWM_DEFAULT_CONFIG,
[1].flags = NRFX_PWM_FLAG_LOOP,
[1].duty_cycles = {0},
[1].n_cycles = 1,
[1].internal_handler = NULL,
[1].cycle_handler = NULL,
[1].cycle_data = NULL,
[1].seq_end_handler = NULL,
[1].seq_end_data = NULL
#endif
#if MYNEWT_VAL(PWM_2)
,
[2].in_use = false,
[2].playing = false,
[2].drv_instance = NRFX_PWM_INSTANCE(2),
[2].config = NRFX_PWM_DEFAULT_CONFIG,
[2].flags = NRFX_PWM_FLAG_LOOP,
[2].duty_cycles = {0},
[2].n_cycles = 1,
[2].internal_handler = NULL,
[2].cycle_handler = NULL,
[2].cycle_data = NULL,
[2].seq_end_handler = NULL,
[2].seq_end_data = NULL
#endif
#if MYNEWT_VAL(PWM_3)
,
[3].in_use = false,
[3].playing = false,
[3].drv_instance = NRFX_PWM_INSTANCE(3),
[3].config = NRFX_PWM_DEFAULT_CONFIG,
[3].flags = NRFX_PWM_FLAG_LOOP,
[3].duty_cycles = {0},
[3].n_cycles = 1,
[3].internal_handler = NULL,
[3].cycle_handler = NULL,
[3].cycle_data = NULL,
[3].seq_end_handler = NULL,
[3].seq_end_data = NULL
#endif
};
#if MYNEWT_VAL(PWM_0)
static void handler_0(nrfx_pwm_evt_type_t event_type)
{
switch (event_type)
{
case NRFX_PWM_EVT_END_SEQ0 :
case NRFX_PWM_EVT_END_SEQ1 :
instances[0].cycle_handler(instances[0].cycle_data);
break;
case NRFX_PWM_EVT_FINISHED :
instances[0].seq_end_handler(instances[0].seq_end_data);
break;
case NRFX_PWM_EVT_STOPPED :
break;
default:
assert(0);
}
}
#endif
#if MYNEWT_VAL(PWM_1)
static void handler_1(nrfx_pwm_evt_type_t event_type)
{
switch (event_type)
{
case NRFX_PWM_EVT_END_SEQ0 :
case NRFX_PWM_EVT_END_SEQ1 :
instances[1].cycle_handler(instances[1].cycle_data);
break;
case NRFX_PWM_EVT_FINISHED :
instances[1].seq_end_handler(instances[1].seq_end_data);
break;
case NRFX_PWM_EVT_STOPPED :
break;
default:
assert(0);
}
}
#endif
#if MYNEWT_VAL(PWM_2)
static void handler_2(nrfx_pwm_evt_type_t event_type)
{
switch (event_type)
{
case NRFX_PWM_EVT_END_SEQ0 :
case NRFX_PWM_EVT_END_SEQ1 :
instances[2].cycle_handler(instances[2].cycle_data);
break;
case NRFX_PWM_EVT_FINISHED :
instances[2].seq_end_handler(instances[2].seq_end_data);
break;
case NRFX_PWM_EVT_STOPPED :
break;
default:
assert(0);
}
}
#endif
#if MYNEWT_VAL(PWM_3)
static void handler_3(nrfx_pwm_evt_type_t event_type)
{
switch (event_type)
{
case NRFX_PWM_EVT_END_SEQ0 :
case NRFX_PWM_EVT_END_SEQ1 :
instances[3].cycle_handler(instances[3].cycle_data);
break;
case NRFX_PWM_EVT_FINISHED :
instances[3].seq_end_handler(instances[3].seq_end_data);
break;
case NRFX_PWM_EVT_STOPPED :
break;
default:
assert(0);
}
}
#endif
static nrfx_pwm_handler_t internal_handlers[] = {
#if MYNEWT_VAL(PWM_0)
handler_0
#endif
#if MYNEWT_VAL(PWM_1)
,
handler_1
#endif
#if MYNEWT_VAL(PWM_2)
,
handler_2
#endif
#if MYNEWT_VAL(PWM_3)
,
handler_3
#endif
};
/**
* Initialize a driver instance.
*/
static int
init_instance(int inst_id, nrfx_pwm_config_t* init_conf)
{
nrfx_pwm_config_t *config;
config = &instances[inst_id].config;
if (!init_conf) {
config->output_pins[0] = NRFX_PWM_PIN_NOT_USED;
config->output_pins[1] = NRFX_PWM_PIN_NOT_USED;
config->output_pins[2] = NRFX_PWM_PIN_NOT_USED;
config->output_pins[3] = NRFX_PWM_PIN_NOT_USED;
config->irq_priority = 3; /* APP_IRQ_PRIORITY_LOW */
config->base_clock = NRF_PWM_CLK_1MHz;
config->count_mode = NRF_PWM_MODE_UP;
config->top_value = 10000;
config->load_mode = NRF_PWM_LOAD_INDIVIDUAL;
config->step_mode = NRF_PWM_STEP_AUTO;
} else {
memcpy(config, init_conf, sizeof(nrfx_pwm_config_t));
}
return (0);
}
/**
* Cleanup a driver instance.
*/
static void
cleanup_instance(int inst_id)
{
instances[inst_id].in_use = false;
instances[inst_id].playing = false;
instances[inst_id].internal_handler = NULL;
instances[inst_id].cycle_handler = NULL;
instances[inst_id].seq_end_handler = NULL;
instances[inst_id].cycle_data = NULL;
instances[inst_id].seq_end_data = NULL;
memset((uint16_t *) &instances[inst_id].duty_cycles,
0x00,
4 * sizeof(uint16_t));
}
/**
* Open the NRF52 PWM device
*
* This function locks the device for access from other tasks.
*
* @param odev The OS device to open
* @param wait The time in MS to wait. If 0 specified, returns immediately
* if resource unavailable. If OS_WAIT_FOREVER specified, blocks
* until resource is available.
* @param arg Argument provided by higher layer to open, in this case
* it can be a nrf_drv_pwm_config_t, to override the default
* configuration.
*
* @return 0 on success, non-zero error code on failure.
*/
static int
nrf52_pwm_open(struct os_dev *odev, uint32_t wait, void *arg)
{
struct pwm_dev *dev;
int stat = 0;
int inst_id;
dev = (struct pwm_dev *) odev;
inst_id = dev->pwm_instance_id;
if (instances[inst_id].in_use) {
return (EINVAL);
}
instances[inst_id].in_use = true;
if (os_started()) {
stat = os_mutex_pend(&dev->pwm_lock, wait);
if (stat != OS_OK) {
return (stat);
}
}
if (odev->od_flags & OS_DEV_F_STATUS_OPEN) {
os_mutex_release(&dev->pwm_lock);
stat = OS_EBUSY;
return (stat);
}
stat = init_instance(inst_id, arg);
if (stat) {
return (stat);
}
return (0);
}
/**
* Close the NRF52 PWM device.
*
* This function unlocks the device.
*
* @param odev The device to close.
*
* @return 0 on success, non-zero error code on failure.
*/
static int
nrf52_pwm_close(struct os_dev *odev)
{
struct pwm_dev *dev;
int inst_id;
dev = (struct pwm_dev *) odev;
inst_id = dev->pwm_instance_id;
if (!instances[inst_id].in_use) {
return (EINVAL);
}
if(instances[inst_id].playing) {
nrfx_pwm_uninit(&instances[inst_id].drv_instance);
}
cleanup_instance(inst_id);
if (os_started()) {
os_mutex_release(&dev->pwm_lock);
}
return (0);
}
/**
* Play using current configuration.
*/
static void
play_current_config(struct nrf52_pwm_dev_global *instance)
{
nrf_pwm_sequence_t const seq =
{
.values.p_individual = &instance->duty_cycles,
.length = 4,
.repeats = 0,
.end_delay = 0
};
nrfx_pwm_simple_playback(&instance->drv_instance,
&seq,
instance->n_cycles,
instance->flags);
}
/**
* Configure a PWM device.
*
* @param dev The device to configure.
* @param cfg Configuration data for this device. If NULL the device will be
* given default configuration values.
*
* @return 0 on success, non-zero error code on failure.
*/
int
nrf52_pwm_configure_device(struct pwm_dev *dev, struct pwm_dev_cfg *cfg)
{
int inst_id = dev->pwm_instance_id;
struct nrf52_pwm_dev_global *instance = &instances[inst_id];
instance->n_cycles = (cfg->n_cycles) ? cfg->n_cycles : 1;
/* Configure Interrupts */
if (cfg->cycle_handler || cfg->seq_end_handler) {
instance->config.irq_priority = cfg->int_prio;
instance->internal_handler = internal_handlers[inst_id];
instance->cycle_handler = (user_handler_t) cfg->cycle_handler;
instance->seq_end_handler = (user_handler_t) cfg->seq_end_handler;
instance->cycle_data = cfg->cycle_data;
instance->seq_end_data = cfg->seq_end_data;
} else {
instance->internal_handler = NULL;
}
instance->flags = (instance->n_cycles > 1) ?
0 :
NRFX_PWM_FLAG_LOOP;
instance->flags |= (instance->cycle_handler) ?
(NRFX_PWM_FLAG_SIGNAL_END_SEQ0 | NRFX_PWM_FLAG_SIGNAL_END_SEQ0) :
0;
instance->flags |= (instance->seq_end_handler) ?
0 :
NRFX_PWM_FLAG_NO_EVT_FINISHED;
if (instance->playing) {
nrfx_pwm_uninit(&instance->drv_instance);
nrfx_pwm_init(&instance->drv_instance,
&instance->config,
instance->internal_handler);
play_current_config(instance);
}
return (0);
}
/**
* Configure a channel on the PWM device.
*
* @param dev The device to configure.
* @param cnum The channel number to configure.
* @param cfg A pwm_chan_cfg data structure, where .
*
* @return 0 on success, non-zero error code on failure.
*/
static int
nrf52_pwm_configure_channel(struct pwm_dev *dev,
uint8_t cnum,
struct pwm_chan_cfg *cfg)
{
int inst_id = dev->pwm_instance_id;
struct nrf52_pwm_dev_global *instance = &instances[inst_id];
nrfx_pwm_config_t *config = &instance->config;
if (!instance->in_use) {
return (EINVAL);
}
config->output_pins[cnum] = (uint8_t) cfg->pin;
config->output_pins[cnum] |= (cfg->inverted) ?
NRFX_PWM_PIN_INVERTED : config->output_pins[cnum];
if (instance->playing) {
nrfx_pwm_uninit(&instance->drv_instance);
nrfx_pwm_init(&instance->drv_instance,
&instance->config,
instance->internal_handler);
play_current_config(instance);
}
return (0);
}
/**
* Set the specified duty cycle on a PWM Channel.
*
* This duty cycle is a fractional duty cycle where 0 == off,
* base_freq / pwm_freq == 100% and any value in between is on for fraction clock
* cycles and off for (base_freq / pwm_freq)-fraction clock cycles.
*
* @param dev The device which the channel belongs to.
* @param cnum The channel number. This channel should be already configured.
* @param fraction The fraction value.
*
* @return 0 on success, non-zero error code on failure.
*/
static int
nrf52_pwm_set_duty_cycle(struct pwm_dev *dev, uint8_t cnum, uint16_t fraction)
{
int inst_id = dev->pwm_instance_id;
nrfx_pwm_config_t *config;
bool inverted;
if (!instances[inst_id].in_use) {
return (EINVAL);
}
config = &instances[inst_id].config;
if (config->output_pins[cnum] == NRFX_PWM_PIN_NOT_USED) {
return (EINVAL);
}
inverted = ((config->output_pins[cnum] & NRFX_PWM_PIN_INVERTED) != 0);
((uint16_t *) &instances[inst_id].duty_cycles)[cnum] =
(inverted) ? fraction : config->top_value - fraction;
return (0);
}
/**
* Enable a given PWM device.
* This device should start playing on its previously configured channels.
*
* @param dev The PWM device to be enabled.
*
* @return 0 on success, non-zero error code on failure.
*/
int
nrf52_pwm_enable(struct pwm_dev *dev)
{
int inst_id = dev->pwm_instance_id;
struct nrf52_pwm_dev_global *instance = &instances[inst_id];
nrfx_pwm_init(&instance->drv_instance,
&instance->config,
instance->internal_handler);
play_current_config(instance);
instance->playing = true;
return (0);
}
/**
* Check whether a PWM channel is enabled on a given device.
*
* @param dev The device which the channel belongs to.
* @param cnum The channel being queried.
*
* @return true if enabled, false if not.
*/
static bool
nrf52_pwm_is_enabled(struct pwm_dev *dev)
{
return (instances[dev->pwm_instance_id].playing);
}
/**
* Disable the PWM device, the device will stop playing while remaining configured.
*
* @param dev The device to disable.
*
* @return 0 on success, non-zero error code on failure.
*/
static int
nrf52_pwm_disable(struct pwm_dev *dev)
{
int inst_id = dev->pwm_instance_id;
if (!instances[inst_id].in_use) {
return (EINVAL);
}
if (!instances[inst_id].playing) {
return (EINVAL);
}
nrfx_pwm_uninit(&instances[inst_id].drv_instance);
instances[inst_id].playing = false;
return (0);
}
/**
* Set the frequency for the device's clock.
* This frequency must be between 1/2 the clock frequency and
* the clock divided by the resolution. NOTE: This will affect
* all PWM channels belonging to the device.
*
* @param dev The device to configure.
* @param freq_hz The frequency value in Hz.
*
* @return A value is in Hz on success, negative error code on failure.
*/
static int
nrf52_pwm_set_frequency(struct pwm_dev *dev, uint32_t freq_hz)
{
int inst_id = dev->pwm_instance_id;
if (!instances[inst_id].in_use) {
return (-EINVAL);
}
nrf_pwm_clk_t *frq = &instances[inst_id].config.base_clock;
uint16_t *top_value = &instances[inst_id].config.top_value;
uint32_t base_freq_val;
freq_hz = (freq_hz > 7999999) ? 7999999 : freq_hz;
freq_hz = (freq_hz < 3) ? 3 : freq_hz;
if (freq_hz > 488) {
*frq = NRF_PWM_CLK_16MHz;
base_freq_val = 16000000;
} else if (freq_hz > 244) {
*frq = NRF_PWM_CLK_8MHz;
base_freq_val = 8000000;
} else if (freq_hz > 122) {
*frq = NRF_PWM_CLK_4MHz;
base_freq_val = 4000000;
} else if (freq_hz > 61) {
*frq = NRF_PWM_CLK_2MHz;
base_freq_val = 2000000;
} else if (freq_hz > 30) {
*frq = NRF_PWM_CLK_1MHz;
base_freq_val = 1000000;
} else if (freq_hz > 14) {
*frq = NRF_PWM_CLK_500kHz;
base_freq_val = 500000;
} else if (freq_hz > 6) {
*frq = NRF_PWM_CLK_250kHz;
base_freq_val = 250000;
} else if (freq_hz > 2) {
*frq = NRF_PWM_CLK_125kHz;
base_freq_val = 125000;
}
*top_value = base_freq_val / freq_hz;
if (instances[inst_id].playing) {
nrfx_pwm_uninit(&instances[inst_id].drv_instance);
nrfx_pwm_init(&instances[inst_id].drv_instance,
&instances[inst_id].config,
instances[inst_id].internal_handler);
play_current_config(&instances[inst_id]);
}
return base_freq_val;
}
/**
* Get the underlying clock driving the PWM device.
*
* @param dev
*
* @return value is in Hz on success, error code on failure.
*/
static int
nrf52_pwm_get_clock_freq(struct pwm_dev *dev)
{
int inst_id = dev->pwm_instance_id;
if (!instances[inst_id].in_use) {
return (-EINVAL);
}
switch (instances[inst_id].config.base_clock) {
case NRF_PWM_CLK_16MHz:
return (16000000);
case NRF_PWM_CLK_8MHz:
return (8000000);
case NRF_PWM_CLK_4MHz:
return (4000000);
case NRF_PWM_CLK_2MHz:
return (2000000);
case NRF_PWM_CLK_1MHz:
return (1000000);
case NRF_PWM_CLK_500kHz:
return (500000);
case NRF_PWM_CLK_250kHz:
return (250000);
case NRF_PWM_CLK_125kHz:
return (125000);
}
return (-EINVAL);
}
/**
* Get the top value for the cycle counter, i.e. the value which sets
* the duty cycle to 100%.
*
* @param dev
*
* @return value in cycles on success, negative error code on failure.
*/
int
nrf52_pwm_get_top_value(struct pwm_dev *dev)
{
int inst_id = dev->pwm_instance_id;
if (!instances[inst_id].in_use) {
return (-EINVAL);
}
return (instances[inst_id].config.top_value);
}
/**
* Get the resolution of the PWM in bits.
*
* @param dev The device to query.
*
* @return The value in bits on success, negative error code on failure.
*/
static int
nrf52_pwm_get_resolution_bits(struct pwm_dev *dev)
{
int inst_id = dev->pwm_instance_id;
if (!instances[inst_id].in_use) {
return (-EINVAL);
}
uint16_t top_val = instances[inst_id].config.top_value;
if (top_val >= 32768)
{
return (15);
} else if (top_val >= 16384) {
return (14);
} else if (top_val >= 8192) {
return (13);
} else if (top_val >= 4096) {
return (12);
} else if (top_val >= 2048) {
return (11);
} else if (top_val >= 1024) {
return (10);
} else if (top_val >= 512) {
return (9);
} else if (top_val >= 256) {
return (8);
} else if (top_val >= 128) {
return (7);
} else if (top_val >= 64) {
return (6);
} else if (top_val >= 32) {
return (5);
} else if (top_val >= 16) {
return (4);
} else if (top_val >= 8) {
return (3);
} else if (top_val >= 4) {
return (2);
} else if (top_val >= 2) {
return (1);
}
return (-EINVAL);
}
#if MYNEWT_VAL(OS_SYSVIEW)
#if MYNEWT_VAL(PWM_0)
static void
pwm_0_irq_handler(void)
{
os_trace_isr_enter();
nrfx_pwm_0_irq_handler();
os_trace_isr_exit();
}
#endif
#if MYNEWT_VAL(PWM_1)
static void
pwm_1_irq_handler(void)
{
os_trace_isr_enter();
nrfx_pwm_1_irq_handler();
os_trace_isr_exit();
}
#endif
#if MYNEWT_VAL(PWM_2)
static void
pwm_2_irq_handler(void)
{
os_trace_isr_enter();
nrfx_pwm_2_irq_handler();
os_trace_isr_exit();
}
#endif
#if MYNEWT_VAL(PWM_3)
static void
pwm_3_irq_handler(void)
{
os_trace_isr_enter();
nrfx_pwm_3_irq_handler();
os_trace_isr_exit();
}
#endif
#define PWM_IRQ_HANDLER(_pwm_no) \
(uint32_t) pwm_ ## _pwm_no ## _irq_handler
#else
#define PWM_IRQ_HANDLER(_pwm_no) \
(uint32_t) nrfx_pwm_ ## _pwm_no ## _irq_handler
#endif
/**
* Callback to initialize an adc_dev structure from the os device
* initialization callback. This sets up a nrf52_pwm_device(), so
* that subsequent lookups to this device allow us to manipulate it.
*/
int
nrf52_pwm_dev_init(struct os_dev *odev, void *arg)
{
struct pwm_dev *dev;
struct pwm_driver_funcs *pwm_funcs;
IRQn_Type irqn;
uint32_t irqh;
assert(odev);
dev = (struct pwm_dev *) odev;
/*
* Originally arg was expected to be a pointer to int with pwm_instance_id,
* however this wastes sizeof(int) of memory only to keep a simple number
* which is used only once. Instead, instance_id can be passed as int casted
* to pointer using helper macro so let's handle both versions here:
* - if number is valid instance_id, let's use it directly
* - otherwise assume it's a valid pointer
*/
if (POINTER_TO_UINT(arg) < NRF52_PWM_MAX_INSTANCES) {
dev->pwm_instance_id = POINTER_TO_UINT(arg);
} else {
dev->pwm_instance_id = *((int*) arg);
assert(dev->pwm_instance_id < NRF52_PWM_MAX_INSTANCES);
}
dev->pwm_chan_count = NRF_PWM_CHANNEL_COUNT;
os_mutex_init(&dev->pwm_lock);
OS_DEV_SETHANDLERS(odev, nrf52_pwm_open, nrf52_pwm_close);
pwm_funcs = &dev->pwm_funcs;
pwm_funcs->pwm_configure_device = nrf52_pwm_configure_device;
pwm_funcs->pwm_configure_channel = nrf52_pwm_configure_channel;
pwm_funcs->pwm_set_duty_cycle = nrf52_pwm_set_duty_cycle;
pwm_funcs->pwm_enable = nrf52_pwm_enable;
pwm_funcs->pwm_is_enabled = nrf52_pwm_is_enabled;
pwm_funcs->pwm_set_frequency = nrf52_pwm_set_frequency;
pwm_funcs->pwm_get_clock_freq = nrf52_pwm_get_clock_freq;
pwm_funcs->pwm_get_top_value = nrf52_pwm_get_top_value;
pwm_funcs->pwm_get_resolution_bits = nrf52_pwm_get_resolution_bits;
pwm_funcs->pwm_disable = nrf52_pwm_disable;
switch (dev->pwm_instance_id) {
#if MYNEWT_VAL(PWM_0)
case 0:
irqn = PWM0_IRQn;
irqh = PWM_IRQ_HANDLER(0);
break;
#endif
#if MYNEWT_VAL(PWM_1)
case 1:
irqn = PWM1_IRQn;
irqh = PWM_IRQ_HANDLER(1);
break;
#endif
#if MYNEWT_VAL(PWM_2)
case 2:
irqn = PWM2_IRQn;
irqh = PWM_IRQ_HANDLER(2);
break;
#endif
#if MYNEWT_VAL(PWM_3)
case 3:
irqn = PWM3_IRQn;
irqh = PWM_IRQ_HANDLER(3);
break;
#endif
default:
assert(0);
return 0;
}
NVIC_SetVector(irqn, irqh);
return (0);
}