/* * (C) Copyright 2014 Leo C. * * SPDX-License-Identifier: GPL-2.0+ */ #include "common.h" #include #include #include "debug.h" #include "gpio.h" /* Pin Name Port Timer Mode max div max div min f [Hz] -------------------------------------------------------------------------------- 0 PG5 OC0B PWM (2**8)*1024 262144 70.31 1 PG4 2 CLK2 PB4 OC2A Toggle (2**8)*1024*2 524288 35.16 3 ZCLK PB5 OC1A PWM (2**16)*1024 67108864 0.2746 4 PB6 OC1B PWM (2**16)*1024 67108864 0.2746 5 PB7 OC0A Toggle (2**8)*1024*2 524288 35.16 6 PG3 7 PG2 8 PG1 9 PG0 10 CLKO PE7 -------------------------------------------------------------------------------- pre Timer0 Timer1 Timer2 -------------------------------------------------- 0 0 0 0 1 1 1 1 2 8 x8 8 x8 8 x8 3 64 x8 64 x8 32 x4 4 256 x4 256 x4 64 x2 5 1024 x4 1024 x4 128 x2 6 256 x2 7 1024 x4 -------------------------------------------------- */ #define PWMTOGGLE 0b01 #define PWMPOS 0b10 #define PWMNEG 0b11 const FLASH uint8_t prescale_factors_01[] = { 8, 8, 4, 4, 0 }; const FLASH uint8_t prescale_factors_2[] = { 8, 4, 2, 2, 2, 4, 0 }; typedef volatile struct { uint8_t pin; uint8_t ddr; uint8_t pout; } port_t ; struct pindef_s { port_t * const adr; const uint8_t mask; #define NO_TIMER 0 #define TIMER0 (1 << 0) #define TIMER1 (2 << 0) #define TIMER2 (3 << 0) #define TIMER (3 << 0) #define T_16BIT (1 << 3) #define CHANA (1 << 4) #define CHANB (0 << 4) const uint8_t timer; }; const FLASH struct pindef_s pinlist[GPIO_MAX] = { { (port_t *) &PING, _BV(5), TIMER0 | CHANB }, { (port_t *) &PING, _BV(4), NO_TIMER }, { (port_t *) &PINB, _BV(4), TIMER2 | CHANA }, { (port_t *) &PINB, _BV(5), TIMER1 | CHANA | T_16BIT }, { (port_t *) &PINB, _BV(6), TIMER1 | CHANB | T_16BIT }, { (port_t *) &PINB, _BV(7), TIMER0 | CHANA }, { (port_t *) &PING, _BV(3), NO_TIMER }, { (port_t *) &PING, _BV(2), NO_TIMER }, { (port_t *) &PING, _BV(1), NO_TIMER }, { (port_t *) &PING, _BV(0), NO_TIMER }, { (port_t *) &PINE, _BV(7), NO_TIMER }, }; void gpio_timer_off(uint8_t timertype) { uint8_t chan_mask; if (timertype & CHANA) chan_mask = 0xc0; else chan_mask = 0x30; switch (timertype & TIMER) { case TIMER0: if (TCCR0A & chan_mask) { TCCR0B = 0; TCCR0A = 0; PRR0 |= _BV(PRTIM0); } break; case TIMER1: if (TCCR1A & chan_mask) { TCCR1B = 0; TCCR1A = 0; PRR0 |= _BV(PRTIM1); } break; case TIMER2: if (TCCR2A & chan_mask) { TCCR2B = 0; TCCR2A = 0; PRR0 |= _BV(PRTIM2); } break; } } int gpio_config(int pin, gpiomode_t mode) { if ((unsigned) pin >= ARRAY_SIZE(pinlist)) { /* Invalid pin number */ return -1; } else { port_t *p = pinlist[pin].adr; uint8_t bit = pinlist[pin].mask; switch (mode) { case INPUT: gpio_timer_off(pinlist[pin].timer); ATOMIC_BLOCK(ATOMIC_FORCEON) { p->ddr &= ~bit; p->pout &= ~bit; } break; case INPUT_PULLUP: gpio_timer_off(pinlist[pin].timer); ATOMIC_BLOCK(ATOMIC_FORCEON) { p->ddr &= ~bit; p->pout |= bit; } break; case OUTPUT: gpio_timer_off(pinlist[pin].timer); case OUTPUT_TIMER: ATOMIC_BLOCK(ATOMIC_FORCEON) { p->ddr |= bit; } break; default: /* Invalid pin mode */ return -1; } } return 0; } void gpio_write(int pin, uint8_t val) { port_t *p = pinlist[pin].adr; uint8_t bit = pinlist[pin].mask; ATOMIC_BLOCK(ATOMIC_FORCEON) { if (val) p->pout |= bit; else p->pout &= ~bit; } } int gpio_read(int pin) { port_t *p = pinlist[pin].adr; uint8_t bit = pinlist[pin].mask; return (p->pin & bit) != 0; } gpiomode_t gpio_config_get(int pin) { uint8_t timertype = pinlist[pin].timer; if (timertype & TIMER) { uint8_t chan_mask; if (timertype & CHANA) chan_mask = 0xc0; else chan_mask = 0x30; switch (timertype & TIMER) { case TIMER0: if (TCCR0A & chan_mask) return OUTPUT_TIMER; break; case TIMER1: if (TCCR1A & chan_mask) return OUTPUT_TIMER; break; case TIMER2: if (TCCR2A & chan_mask) return OUTPUT_TIMER; break; } } port_t *p = pinlist[pin].adr; uint8_t bit = pinlist[pin].mask; if (p->ddr & bit) return OUTPUT; if (p->pout & bit) return INPUT_PULLUP; return INPUT; } /* * return -1: pin has no timer output * 0: pin is not configured for timer output * > 0: divider */ long gpio_clockdiv_get(int pin) { long divider; uint8_t prescale; const FLASH uint8_t *pstab; uint8_t timertype = pinlist[pin].timer; if ((timertype & TIMER) == 0) return -1; if (gpio_config_get(pin) != OUTPUT_TIMER) return 0; switch (timertype & TIMER) { case TIMER0: prescale = TCCR0B; divider = OCR0A; break; case TIMER1: prescale = TCCR1B; divider = ICR1; break; case TIMER2: prescale = TCCR2B; divider = OCR2A; break; } prescale = (prescale & 0x07) - 1; divider += 1; pstab = (timertype & TIMER) == TIMER2 ? prescale_factors_2 : prescale_factors_01; while (prescale--) divider *= pstab[prescale]; if ((timertype & (CHANA|T_16BIT)) == CHANA) divider *= 2; return divider; } int gpio_clockdiv_set(int pin, unsigned long divider) { unsigned long ltop; uint16_t top; uint8_t prescale; const FLASH uint8_t *pstab; uint8_t timertype = pinlist[pin].timer; if ((timertype & TIMER) == 0) return 0; if (divider < 2) return -1; ltop = divider; if ((timertype & (CHANA|T_16BIT)) == CHANA) ltop /= 2; if (ltop > 1024 * ((timertype & T_16BIT) ? (1L<<16) : (1L<<8))) return -1; prescale = 1; pstab = (timertype & TIMER) == TIMER2 ? prescale_factors_2 : prescale_factors_01; // debug("** clockdiv_set: pin: %d, ltop: %lu, prescale: %d\n", // pin, ltop, prescale); while (ltop > ((timertype & T_16BIT) ? (1L<<16) : (1L<<8))) { // debug("** clockdiv_set: pin: %d, ltop: %lu, prescale: %d, *pstab %d\n", // pin, ltop, prescale, *pstab); if (*pstab == 0) return -1; ltop /= *pstab++; prescale++; } if (ltop == 0) return -1; top = ltop - 1; // PING |= _BV(0); /* Debug */ switch (timertype & TIMER) { case TIMER0: PRR0 &= ~_BV(PRTIM0); TCCR0B = (1 << WGM02); TCNT0 = 0; OCR0A = top; if (timertype & CHANA) { TCCR0A = (PWMTOGGLE << COM0A0) | (0b11 << WGM00); } else { OCR0B = top/2; TCCR0A = (PWMPOS << COM0B0) | (0b11 << WGM10); } TCCR0B = (1 << WGM02) | (prescale << CS10); break; case TIMER1: PRR0 &= ~_BV(PRTIM1); TCCR1B = (0b11 << WGM12); TCNT1 = 0; ICR1 = top; if (timertype & CHANA) { OCR1A = top/2; TCCR1A = (PWMPOS << COM1A0) | (0b10 << WGM10); } else { OCR1B = top/2; TCCR1A = (PWMPOS << COM1B0) | (0b10 << WGM10); } // debug("pin: %d, top: %u," // " ICR1: %u, OCR1A: %u, OCR1B: %u\n", // pin, top, ICR1, OCR1A, OCR1B); TCCR1B = (0b11 << WGM12) | (prescale << CS10); break; case TIMER2: PRR0 &= ~_BV(PRTIM2); TCCR2B = (1 << WGM22); TCNT2 = 0; OCR2A = top; if (timertype & CHANA) { TCCR2A = (PWMTOGGLE << COM2A0) | (0b11 << WGM20); } else { OCR2B = top/2; TCCR2A = (PWMPOS << COM2B0) | (0b11 << WGM10); } TCCR2B = (1 << WGM22) | (prescale << CS10); break; } // PING |= _BV(0); /* Debug */ gpio_config(pin, OUTPUT_TIMER); return 0; }