| /* |
| * 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 <errno.h> |
| #include <string.h> |
| #include "os/mynewt.h" |
| #include <pwm/pwm.h> |
| #include <hal/hal_bsp.h> |
| #include <hal/hal_gpio.h> |
| |
| #define BASE_FREQ MYNEWT_VAL(OS_CPUTIME_FREQ) |
| #define MAX_FREQ BASE_FREQ / 2 |
| #define DEV_COUNT MYNEWT_VAL(SOFT_PWM_DEVS) |
| #define CHAN_COUNT MYNEWT_VAL(SOFT_PWM_CHANS) |
| #define NO_PIN 0xff |
| |
| struct soft_pwm_channel { |
| uint8_t pin; |
| bool inverted; |
| uint16_t fraction; |
| bool running; |
| struct hal_timer toggle_timer; |
| }; |
| |
| struct soft_pwm_dev { |
| bool playing; |
| uint32_t frequency; |
| uint16_t top_value; |
| uint32_t n_cycles; |
| uint32_t cycle_cnt; |
| user_handler_t cycle_handler; |
| user_handler_t seq_end_handler; |
| void* cycle_data; |
| void* seq_end_data; |
| struct hal_timer cycle_timer; |
| struct soft_pwm_channel chans[CHAN_COUNT]; |
| }; |
| |
| static struct soft_pwm_dev instances[DEV_COUNT]; |
| |
| /** |
| * Cycle start callback |
| * |
| * Initializes every channel's output to high (or low accrding to its polarity). |
| * Schedules toggle_cb for every channel. |
| */ |
| static void cycle_cb(void* arg) |
| { |
| int cnum; |
| bool inverted; |
| struct soft_pwm_dev* instance = (struct soft_pwm_dev *) arg; |
| struct soft_pwm_channel* chans = instance->chans; |
| uint32_t now = os_cputime_get32(); |
| |
| if (instance->n_cycles) { |
| instance->cycle_cnt++; |
| instance->playing = (instance->cycle_cnt < instance->n_cycles); |
| } |
| |
| if (instance->playing) { |
| for (cnum = 0; cnum < CHAN_COUNT; cnum++) { |
| if (chans[cnum].running) { |
| inverted = chans[cnum].inverted; |
| hal_gpio_write(chans[cnum].pin, (inverted) ? 0 : 1); |
| |
| os_cputime_timer_start(&chans[cnum].toggle_timer, |
| now + chans[cnum].fraction); |
| } |
| } |
| os_cputime_timer_start(&instance->cycle_timer, |
| now + instance->top_value); |
| |
| if (instance->cycle_handler) { |
| instance->cycle_handler(instance->cycle_data); |
| } |
| |
| } else if (instance->seq_end_handler) { |
| instance->seq_end_handler(instance->seq_end_data); |
| } |
| } |
| |
| /** |
| * Channel output toggle callback |
| * |
| * Toggles a channel's output. |
| */ |
| static void toggle_cb(void* arg) |
| { |
| uint32_t *pin = (uint32_t *) arg; |
| hal_gpio_toggle(*pin); |
| } |
| |
| /** |
| * Open the Soft 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 |
| * @return 0 on success, non-zero on failure. |
| */ |
| static int |
| soft_pwm_open(struct os_dev *odev, uint32_t wait, void *arg) |
| { |
| uint32_t cnum; |
| uint32_t stat = 0; |
| struct pwm_dev *dev = (struct pwm_dev *) odev; |
| struct soft_pwm_dev *instance = &instances[dev->pwm_instance_id]; |
| |
| 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); |
| } |
| |
| instance->frequency = 100; |
| instance->top_value = BASE_FREQ / 100; |
| os_cputime_timer_init(&instance->cycle_timer, |
| cycle_cb, |
| &instances[dev->pwm_instance_id]); |
| |
| instance->playing = false; |
| for (cnum = 0; cnum < CHAN_COUNT; cnum++) { |
| instance->chans[cnum].pin = NO_PIN; |
| instance->chans[cnum].fraction = 0; |
| instance->chans[cnum].inverted = false; |
| instance->chans[cnum].running = false; |
| os_cputime_timer_init(&instance->chans[cnum].toggle_timer, |
| toggle_cb, |
| &instance->chans[cnum].pin); |
| } |
| |
| return (0); |
| } |
| |
| /** |
| * Close the Soft PWM device. |
| * |
| * This function unlocks the device. |
| * |
| * @param odev The device to close. |
| */ |
| static int |
| soft_pwm_close(struct os_dev *odev) |
| { |
| uint8_t cnum; |
| struct pwm_dev *dev = (struct pwm_dev *) odev; |
| struct soft_pwm_dev *instance = &instances[dev->pwm_instance_id]; |
| |
| os_cputime_timer_stop(&instance->cycle_timer); |
| for (cnum = 0; cnum < CHAN_COUNT; cnum++) { |
| os_cputime_timer_stop(&instance->chans[cnum].toggle_timer); |
| } |
| |
| if (os_started()) { |
| os_mutex_release(&dev->pwm_lock); |
| } |
| |
| return (0); |
| } |
| |
| /** |
| * 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 |
| soft_pwm_configure_device(struct pwm_dev *dev, struct pwm_dev_cfg *cfg) |
| { |
| struct soft_pwm_dev *instance = &instances[dev->pwm_instance_id]; |
| |
| if (!cfg) { |
| instance->cycle_handler = NULL; |
| instance->seq_end_handler = NULL; |
| instance->cycle_data = NULL; |
| instance->seq_end_data = NULL; |
| cfg->n_cycles = 0; |
| return (0); |
| } |
| |
| instance->n_cycles = (cfg->n_cycles) ? cfg->n_cycles : 0; |
| |
| /* Configure Interrupts */ |
| if (cfg->cycle_handler || cfg->seq_end_handler) { |
| 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; |
| } |
| |
| return (0); |
| } |
| |
| /** |
| * Configure a channel on the PWM device. |
| * |
| * @param dev The device to configure. |
| * @param cnum The channel number to configure. |
| * @param cfg Configuration data for this channel. If NULL the channel will be |
| * disabled or given default configuration values. |
| * |
| * @return 0 on success, non-zero error code on failure. |
| */ |
| static int |
| soft_pwm_configure_channel(struct pwm_dev *dev, |
| uint8_t cnum, |
| struct pwm_chan_cfg *cfg) |
| { |
| uint32_t last_pin; |
| struct soft_pwm_dev *instance = &instances[dev->pwm_instance_id]; |
| struct soft_pwm_channel *chan = &instance->chans[cnum]; |
| |
| last_pin = (chan->pin == NO_PIN) ? |
| cfg->pin : |
| chan->pin; |
| |
| /* Set the previously used pin to low */ |
| if (cfg->pin != last_pin) { |
| if (chan->running) { |
| chan->running = false; |
| os_cputime_timer_stop(&chan->toggle_timer); |
| } |
| hal_gpio_write(last_pin, 0); |
| } |
| |
| if (cfg) { |
| chan->pin = cfg->pin; |
| chan->inverted = cfg->inverted; |
| hal_gpio_init_out(cfg->pin, (cfg->inverted) ? 1 : 0); |
| } else { /* unconfigure the channel */ |
| chan->pin = NO_PIN; |
| chan->inverted = false; |
| chan->fraction = 0; |
| chan->running = false; |
| } |
| |
| return (0); |
| } |
| |
| /** |
| * Enable the PWM with specified duty cycle. |
| * |
| * This duty cycle is a fractional duty cycle where 0 == off, clk_freq/pwm_freq=on, |
| * and any value in between is on for fraction clocks and off |
| * for 65535-fraction clocks. |
| * |
| * @param dev The device to configure. |
| * @param cnum The channel number. The channel should be in use. |
| * @param fraction The fraction value. |
| * |
| * @return 0 on success, negative on error. |
| */ |
| static int |
| soft_pwm_enable(struct pwm_dev *dev) |
| { |
| uint8_t cnum; |
| bool level; |
| struct soft_pwm_dev* instance = &instances[dev->pwm_instance_id]; |
| struct soft_pwm_channel* chans = instance->chans; |
| |
| /* Handling 0% and 100% duty cycle channels. */ |
| for (cnum = 0; cnum < CHAN_COUNT; cnum++) { |
| if ((chans[cnum].pin != NO_PIN) && (!chans[cnum].running)) { |
| level = (chans[cnum].inverted && chans[cnum].fraction == 0) || |
| (!chans[cnum].inverted && chans[cnum].fraction != 0); |
| hal_gpio_write(chans[cnum].pin, (level) ? 1 : 0); |
| } |
| } |
| |
| if (instance->n_cycles) { |
| instance->cycle_cnt = 0; |
| } |
| |
| instance->playing = true; |
| cycle_cb(instance); |
| return (0); |
| } |
| |
| /** |
| * Enable the PWM with specified duty cycle. |
| * |
| * This duty cycle is a fractional duty cycle where 0 == off, clk_freq/pwm_freq=on, |
| * and any value in between is on for fraction clocks and off |
| * for 65535-fraction clocks. |
| * |
| * @param dev The device to configure. |
| * @param cnum The channel number. The channel should be in use. |
| * @param fraction The fraction value. |
| * |
| * @return 0 on success, negative on error. |
| */ |
| static int |
| soft_pwm_set_duty_cycle(struct pwm_dev *dev, uint8_t cnum, uint16_t fraction) |
| { |
| bool level; |
| struct soft_pwm_dev* instance = &instances[dev->pwm_instance_id]; |
| struct soft_pwm_channel* chan = &instance->chans[cnum]; |
| assert (chan->pin != NO_PIN); |
| |
| chan->fraction = (fraction <= instance->top_value) ? |
| fraction : |
| instance->top_value; |
| |
| /* Handling 0% and 100% duty cycles. */ |
| if ((fraction < instance->top_value) && (fraction > 0)) { |
| chan->running = true; |
| } else { |
| chan->running = false; |
| os_cputime_timer_stop(&chan->toggle_timer); |
| if (instance->playing) { |
| level = (chan->inverted && fraction == 0) || |
| (!chan->inverted && fraction != 0); |
| hal_gpio_write(chan->pin, (level) ? 1 : 0); |
| } |
| } |
| 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 |
| soft_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, negative on error. |
| */ |
| static int |
| soft_pwm_disable(struct pwm_dev *dev) |
| { |
| uint8_t cnum; |
| struct soft_pwm_dev* instance = &instances[dev->pwm_instance_id]; |
| instance->playing = false; |
| os_cputime_timer_stop(&instance->cycle_timer); |
| |
| for (cnum = 0; cnum < CHAN_COUNT; cnum++) { |
| if (instance->chans[cnum].pin != NO_PIN) { |
| os_cputime_timer_stop(&instance->chans[cnum].toggle_timer); |
| hal_gpio_write(instance->chans[cnum].pin, 0); |
| } |
| } |
| return (0); |
| } |
| |
| /** |
| * This frequency must be between 1/2 the clock frequency and |
| * the clock divided by the resolution. NOTE: This may affect |
| * other PWM channels. |
| * |
| * @param dev The device to configure. |
| * @param freq_hz The frequency value in Hz. |
| * |
| * @return A value is in Hz on success, negative on error. |
| */ |
| static int |
| soft_pwm_set_frequency(struct pwm_dev *dev, uint32_t freq_hz) |
| { |
| freq_hz = (freq_hz > MAX_FREQ) ? MAX_FREQ : freq_hz; |
| freq_hz = (freq_hz < 2) ? 2 : freq_hz; |
| |
| instances[dev->pwm_instance_id].top_value = BASE_FREQ / freq_hz; |
| |
| return BASE_FREQ; |
| } |
| |
| /** |
| * Get the underlying clock driving the PWM device. |
| * |
| * @param dev |
| * |
| * @return value is in Hz on success, negative on error. |
| */ |
| static int |
| soft_pwm_get_clock_freq(struct pwm_dev *dev) |
| { |
| return BASE_FREQ; |
| } |
| |
| /** |
| * 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 on error. |
| */ |
| int |
| soft_pwm_get_top_value(struct pwm_dev *dev) |
| { |
| return (instances[dev->pwm_instance_id].top_value); |
| } |
| |
| /** |
| * Get the resolution of the PWM in bits. |
| * |
| * @param dev The device to query. |
| * |
| * @return The value in bits on success, negative on error. |
| */ |
| static int |
| soft_pwm_get_resolution_bits(struct pwm_dev *dev) |
| { |
| int shamt; |
| uint16_t mask = 0x8000; |
| uint16_t top_val = instances[dev->pwm_instance_id].top_value; |
| |
| for (shamt = 0; shamt < 15; shamt++) { |
| if (top_val & mask) { |
| break; |
| } |
| mask >>= 1; |
| } |
| |
| return (16 - shamt - 1); |
| } |
| |
| /** |
| * Callback to initialize an adc_dev structure from the os device |
| * initialization callback. This sets up a soft_pwm_device(), so |
| * that subsequent lookups to this device allow us to manipulate it. |
| */ |
| int |
| soft_pwm_dev_init(struct os_dev *odev, void *arg) |
| { |
| struct pwm_dev *dev; |
| struct pwm_driver_funcs *pwm_funcs; |
| |
| 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) < DEV_COUNT) { |
| dev->pwm_instance_id = POINTER_TO_UINT(arg); |
| } else { |
| dev->pwm_instance_id = *((int*) arg); |
| assert(dev->pwm_instance_id < DEV_COUNT); |
| } |
| |
| dev->pwm_chan_count = CHAN_COUNT; |
| os_mutex_init(&dev->pwm_lock); |
| |
| OS_DEV_SETHANDLERS(odev, soft_pwm_open, soft_pwm_close); |
| |
| pwm_funcs = &dev->pwm_funcs; |
| pwm_funcs->pwm_configure_device = soft_pwm_configure_device; |
| pwm_funcs->pwm_configure_channel = soft_pwm_configure_channel; |
| pwm_funcs->pwm_set_duty_cycle = soft_pwm_set_duty_cycle; |
| pwm_funcs->pwm_enable = soft_pwm_enable; |
| pwm_funcs->pwm_is_enabled = soft_pwm_is_enabled; |
| pwm_funcs->pwm_set_frequency = soft_pwm_set_frequency; |
| pwm_funcs->pwm_get_clock_freq = soft_pwm_get_clock_freq; |
| pwm_funcs->pwm_get_top_value = soft_pwm_get_top_value; |
| pwm_funcs->pwm_get_resolution_bits = soft_pwm_get_resolution_bits; |
| pwm_funcs->pwm_disable = soft_pwm_disable; |
| |
| return (0); |
| } |