| /** |
| * 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 <stdint.h> |
| #include <string.h> |
| #include "os/mynewt.h" |
| #include <xc.h> |
| #include "hal/hal_timer.h" |
| #include "mcu/mips_hal.h" |
| |
| #define PIC32MX_TIMER_COUNT (4) |
| #define PIC32MX_PRESCALER_COUNT (8) |
| |
| #define TxCON(T) (base_address[T][0x0 / 0x4]) |
| #define TxCONCLR(T) (base_address[T][0x4 / 0x4]) |
| #define TxCONSET(T) (base_address[T][0x8 / 0x4]) |
| #define TMRx(T) (base_address[T][0x10 / 0x4]) |
| #define PRx(T) (base_address[T][0x20 / 0x4]) |
| |
| static volatile uint32_t * base_address[PIC32MX_TIMER_COUNT] = { |
| (volatile uint32_t *)_TMR2_BASE_ADDRESS, |
| (volatile uint32_t *)_TMR3_BASE_ADDRESS, |
| (volatile uint32_t *)_TMR4_BASE_ADDRESS, |
| (volatile uint32_t *)_TMR5_BASE_ADDRESS, |
| }; |
| |
| static uint32_t timer_prescalers[PIC32MX_PRESCALER_COUNT] = |
| {1, 2, 4, 8, 16, 32, 64, 256}; |
| |
| struct pic32_timer { |
| uint32_t index; |
| uint32_t counter; |
| uint32_t frequency; /* Holds the true frequency of the timer */ |
| TAILQ_HEAD(hal_timer_qhead, hal_timer) hal_timer_queue; |
| }; |
| static struct pic32_timer timers[PIC32MX_TIMER_COUNT]; |
| |
| |
| static inline uint32_t |
| hal_timer_get_prescaler(int timer_num) |
| { |
| uint32_t index = |
| (TxCON(timer_num) & _T2CON_TCKPS_MASK) >> _T2CON_TCKPS_POSITION; |
| return timer_prescalers[index]; |
| } |
| |
| static inline uint32_t |
| hal_timer_get_peripheral_base_clock(void) |
| { |
| uint32_t divisor = 1; |
| divisor <<= (OSCCON & _OSCCON_PBDIV_MASK) >> _OSCCON_PBDIV_POSITION; |
| return MYNEWT_VAL(CLOCK_FREQ) / divisor; |
| } |
| |
| static void |
| hal_timer_enable_int(int timer_num) |
| { |
| switch (timer_num) { |
| case 0: |
| IPC2CLR = _IPC2_T2IP_MASK | _IPC2_T2IS_MASK; |
| IPC2SET = 3 << _IPC2_T2IP_POSITION; |
| IFS0CLR = _IFS0_T2IF_MASK; |
| IEC0SET = _IEC0_T2IE_MASK; |
| break; |
| case 1: |
| IPC3CLR = _IPC3_T3IP_MASK | _IPC3_T3IS_MASK; |
| IPC3SET = 3 << _IPC3_T3IP_POSITION; |
| IFS0CLR = _IFS0_T3IF_MASK; |
| IEC0SET = _IEC0_T3IE_MASK; |
| break; |
| case 2: |
| IPC4CLR = _IPC4_T4IP_MASK | _IPC4_T4IS_MASK; |
| IPC4SET = 3 << _IPC4_T4IP_POSITION; |
| IFS0CLR = _IFS0_T4IF_MASK; |
| IEC0SET = _IEC0_T4IE_MASK; |
| break; |
| case 3: |
| IPC5CLR = _IPC5_T5IP_MASK | _IPC5_T5IS_MASK; |
| IPC5SET = 3 << _IPC5_T5IP_POSITION; |
| IFS0CLR = _IFS0_T5IF_MASK; |
| IEC0SET = _IEC0_T5IE_MASK; |
| break; |
| } |
| } |
| |
| static void |
| hal_timer_disable_int(int timer_num) |
| { |
| switch (timer_num) { |
| case 0: |
| IEC0CLR = _IEC0_T2IE_MASK; |
| IFS0CLR = _IFS0_T2IF_MASK; |
| break; |
| case 1: |
| IEC0CLR = _IEC0_T3IE_MASK; |
| IFS0CLR = _IFS0_T3IF_MASK; |
| break; |
| case 2: |
| IEC0CLR = _IEC0_T4IE_MASK; |
| IFS0CLR = _IFS0_T4IF_MASK; |
| break; |
| case 3: |
| IEC0CLR = _IEC0_T5IE_MASK; |
| IFS0CLR = _IFS0_T5IF_MASK; |
| break; |
| } |
| |
| } |
| |
| static void |
| update_period_register(int timer_num) |
| { |
| if (TAILQ_EMPTY(&timers[timer_num].hal_timer_queue)) { |
| PRx(timer_num) = UINT16_MAX; |
| } else { |
| struct hal_timer *first = TAILQ_FIRST( |
| &timers[timer_num].hal_timer_queue); |
| uint32_t ticks = hal_timer_read(timer_num); |
| |
| if (ticks >= first->expiry) { |
| /* |
| * Create a timer interrupt immediately. This case must never |
| * execute inside the interrupt handler (otherwise we would skip |
| * the interrupt). |
| */ |
| PRx(timer_num) = TMRx(timer_num) + 1; |
| } else { |
| uint32_t delta = ticks - first->expiry; |
| if (delta > UINT16_MAX) |
| delta = UINT16_MAX; |
| |
| PRx(timer_num) = delta; |
| } |
| } |
| } |
| |
| static inline void |
| update_counter(int timer_num) |
| { |
| timers[timer_num].counter += PRx(timer_num); |
| } |
| |
| static void |
| handle_timer_list(int timer_num) |
| { |
| uint32_t current_tick = hal_timer_read(timer_num); |
| struct hal_timer *entry; |
| |
| while ((entry = TAILQ_FIRST(&timers[timer_num].hal_timer_queue)) != |
| NULL) { |
| if (entry->expiry <= current_tick) { |
| TAILQ_REMOVE(&timers[timer_num].hal_timer_queue, entry, link); |
| entry->link.tqe_prev = NULL; |
| entry->link.tqe_next = NULL; |
| entry->cb_func(entry->cb_arg); |
| } else { |
| break; |
| } |
| } |
| |
| /* |
| * Even if the list is left unchanged, the period register still needs to |
| * be computed again to ensure that the first callback in the list will |
| * be called on time. |
| */ |
| update_period_register(timer_num); |
| } |
| |
| void |
| __attribute__((interrupt(IPL3AUTO), vector(_TIMER_2_VECTOR))) |
| timer2_isr(void) |
| { |
| update_counter(0); |
| handle_timer_list(0); |
| |
| IFS0CLR = _IFS0_T2IF_MASK; |
| } |
| |
| void |
| __attribute__((interrupt(IPL3AUTO), vector(_TIMER_3_VECTOR))) |
| timer3_isr(void) |
| { |
| update_counter(1); |
| handle_timer_list(1); |
| |
| IFS0CLR = _IFS0_T3IF_MASK; |
| } |
| |
| void |
| __attribute__((interrupt(IPL3AUTO), vector(_TIMER_4_VECTOR))) |
| timer4_isr(void) |
| { |
| update_counter(2); |
| handle_timer_list(2); |
| |
| IFS0CLR = _IFS0_T4IF_MASK; |
| } |
| |
| void |
| __attribute__((interrupt(IPL3AUTO), vector(_TIMER_5_VECTOR))) |
| timer5_isr(void) |
| { |
| update_counter(3); |
| handle_timer_list(3); |
| |
| IFS0CLR = _IFS0_T5IF_MASK; |
| } |
| |
| int |
| hal_timer_init(int timer_num, void *cfg) |
| { |
| if (timer_num >= PIC32MX_TIMER_COUNT) |
| return -1; |
| |
| TxCON(timer_num) = 0; |
| timers[timer_num].index = timer_num; |
| timers[timer_num].counter = 0; |
| |
| hal_timer_enable_int(timer_num); |
| |
| return 0; |
| } |
| |
| int |
| hal_timer_deinit(int timer_num) |
| { |
| struct hal_timer *timer; |
| |
| if (timer_num >= PIC32MX_TIMER_COUNT) { |
| return -1; |
| } |
| |
| TxCON(timer_num) = 0; |
| hal_timer_disable_int(timer_num); |
| while ((timer = TAILQ_FIRST(&timers[timer_num].hal_timer_queue)) != |
| NULL) { |
| TAILQ_REMOVE(&timers[timer_num].hal_timer_queue, timer, link); |
| } |
| |
| return 0; |
| } |
| |
| int |
| hal_timer_config(int timer_num, uint32_t freq_hz) |
| { |
| int i; |
| uint32_t pr; |
| uint32_t ideal_prescaler; |
| |
| if (timer_num >= PIC32MX_TIMER_COUNT) { |
| return -1; |
| } |
| |
| if (freq_hz == 0) { |
| return -1; |
| } |
| |
| ideal_prescaler = hal_timer_get_peripheral_base_clock() / freq_hz; |
| if (ideal_prescaler > 256) { |
| return -1; |
| } |
| |
| if (ideal_prescaler == 1) { |
| i = 0; |
| } else { |
| /* Find closest prescaler */ |
| for (i = 1; i < PIC32MX_PRESCALER_COUNT; ++i) { |
| if (ideal_prescaler <= timer_prescalers[i]) { |
| uint32_t min_delta = ideal_prescaler - timer_prescalers[i - 1]; |
| uint32_t max_delta = timer_prescalers[i] - ideal_prescaler; |
| if (min_delta < max_delta) { |
| i -= 1; |
| } |
| break; |
| } |
| } |
| } |
| |
| TxCON(timer_num) = 0; |
| |
| TxCONCLR(timer_num) = _T2CON_TCKPS_MASK; |
| TxCONSET(timer_num) = (i << _T2CON_TCKPS_POSITION) & _T2CON_TCKPS_MASK; |
| |
| /* Set PR to its maximum value to minimize timer interrupts */ |
| PRx(timer_num) = UINT16_MAX; |
| TMRx(timer_num) = 0; |
| |
| timers[timer_num].frequency = hal_timer_get_peripheral_base_clock() / |
| timer_prescalers[i]; |
| |
| /* Start timer */ |
| TxCONSET(timer_num) = _T2CON_TON_MASK; |
| |
| return 0; |
| } |
| |
| uint32_t |
| hal_timer_get_resolution(int timer_num) |
| { |
| if (timer_num >= PIC32MX_TIMER_COUNT) { |
| return 0; |
| } |
| |
| return 1000000000 / timers[timer_num].frequency; |
| } |
| |
| uint32_t |
| hal_timer_read(int timer_num) |
| { |
| uint32_t tmr, counter, ctx; |
| |
| if (timer_num >= PIC32MX_TIMER_COUNT) { |
| return 0; |
| } |
| |
| __HAL_DISABLE_INTERRUPTS(ctx); |
| |
| tmr = TMRx(timer_num); |
| counter = timers[timer_num].counter; |
| |
| __HAL_ENABLE_INTERRUPTS(ctx); |
| |
| return tmr + counter; |
| } |
| |
| int |
| hal_timer_delay(int timer_num, uint32_t ticks) |
| { |
| uint32_t until; |
| |
| if (timer_num >= PIC32MX_TIMER_COUNT) { |
| return -1; |
| } |
| |
| until = hal_timer_read(timer_num) + ticks; |
| while ((int32_t)(hal_timer_read(timer_num) - until) <= 0) { |
| } |
| |
| return 0; |
| } |
| |
| int |
| hal_timer_set_cb(int timer_num, struct hal_timer *timer, hal_timer_cb cb_func, |
| void *arg) |
| { |
| if (timer_num >= PIC32MX_TIMER_COUNT) { |
| return -1; |
| } |
| |
| memset(timer, 0, sizeof(struct hal_timer)); |
| timer->bsp_timer = &timers[timer_num]; |
| timer->cb_func = cb_func; |
| timer->cb_arg = arg; |
| |
| return 0; |
| } |
| |
| int |
| hal_timer_start(struct hal_timer *timer, uint32_t ticks) |
| { |
| uint32_t tick; |
| struct pic32_timer *bsp_timer; |
| |
| if (timer == NULL || ticks == 0) { |
| return -1; |
| } |
| |
| bsp_timer = (struct pic32_timer *)(timer->bsp_timer); |
| if (bsp_timer == NULL) { |
| return -1; |
| } |
| |
| tick = hal_timer_read(bsp_timer->index) + ticks; |
| return hal_timer_start_at(timer, tick); |
| } |
| |
| int |
| hal_timer_start_at(struct hal_timer *timer, uint32_t tick) |
| { |
| os_sr_t ctx; |
| struct pic32_timer *bsp_timer; |
| |
| if ((timer == NULL) || (timer->link.tqe_prev != NULL) || |
| (timer->cb_func == NULL)) { |
| return -1; |
| } |
| |
| bsp_timer = (struct pic32_timer *)timer->bsp_timer; |
| if (bsp_timer == NULL) { |
| return -1; |
| } |
| |
| timer->expiry = tick; |
| |
| __HAL_DISABLE_INTERRUPTS(ctx); |
| |
| /* Add to callback queue, order using expiry tick in increasing order */ |
| if (TAILQ_EMPTY(&bsp_timer->hal_timer_queue)) { |
| TAILQ_INSERT_HEAD(&bsp_timer->hal_timer_queue, timer, link); |
| } else { |
| struct hal_timer *entry; |
| TAILQ_FOREACH(entry, &bsp_timer->hal_timer_queue, link) { |
| if ((int32_t)(timer->expiry - entry->expiry) < 0) { |
| TAILQ_INSERT_BEFORE(entry, timer, link); |
| break; |
| } |
| } |
| if (!entry) { |
| TAILQ_INSERT_TAIL(&bsp_timer->hal_timer_queue, timer, link); |
| } |
| } |
| |
| update_period_register(bsp_timer->index); |
| |
| __HAL_ENABLE_INTERRUPTS(ctx); |
| |
| return 0; |
| } |
| |
| int |
| hal_timer_stop(struct hal_timer *timer) |
| { |
| os_sr_t ctx; |
| struct pic32_timer *bsp_timer; |
| |
| if (timer == NULL) { |
| return -1; |
| } |
| |
| bsp_timer = (struct pic32_timer *)timer->bsp_timer; |
| if (bsp_timer == NULL) { |
| return -1; |
| } |
| |
| __HAL_DISABLE_INTERRUPTS(ctx); |
| |
| if (timer->link.tqe_prev != NULL) { |
| TAILQ_REMOVE(&bsp_timer->hal_timer_queue, timer, link); |
| timer->link.tqe_prev = NULL; |
| timer->link.tqe_next = NULL; |
| } |
| |
| update_period_register(bsp_timer->index); |
| |
| __HAL_ENABLE_INTERRUPTS(ctx); |
| |
| return 0; |
| } |