github.com/usbarmory/tamago@v0.0.0-20240508072735-8612bbe1e454/soc/bcm2835/gpio.go (about) 1 // BCM2835 SoC GPIO support 2 // https://github.com/usbarmory/tamago 3 // 4 // Copyright (c) the bcm2835 package authors 5 // 6 // Use of this source code is governed by the license 7 // that can be found in the LICENSE file. 8 9 package bcm2835 10 11 import ( 12 "fmt" 13 "sync" 14 15 "github.com/usbarmory/tamago/arm" 16 "github.com/usbarmory/tamago/internal/reg" 17 ) 18 19 // GPIO registers 20 const ( 21 GPIO_BASE = 0x200000 22 23 GPFSEL0 = GPIO_BASE 24 GPFSEL1 = GPIO_BASE + 0x04 25 GPSET0 = GPIO_BASE + 0x1c 26 GPCLR0 = GPIO_BASE + 0x28 27 GPLEV0 = GPIO_BASE + 0x34 28 GPPUD = GPIO_BASE + 0x94 29 GPPUDCLK0 = GPIO_BASE + 0x98 30 ) 31 32 // GPIO function selections (p92, BCM2835 ARM Peripherals) 33 const ( 34 GPIO_INPUT = 0b000 35 GPIO_OUTPUT = 0b001 36 GPIO_FN0 = 0b100 37 GPIO_FN1 = 0b101 38 GPIO_FN2 = 0b110 39 GPIO_FN3 = 0b111 40 GPIO_FN4 = 0b011 41 GPIO_FN5 = 0b010 42 ) 43 44 // GPIO instance 45 type GPIO struct { 46 num int 47 } 48 49 type GPIOFunction uint32 50 51 var gpmux = sync.Mutex{} 52 53 // NewGPIO gets access to a single GPIO line 54 func NewGPIO(num int) (*GPIO, error) { 55 if num > 54 || num < 0 { 56 return nil, fmt.Errorf("invalid GPIO number %d", num) 57 } 58 59 return &GPIO{num: num}, nil 60 } 61 62 // Out configures a GPIO as output. 63 func (gpio *GPIO) Out() { 64 gpio.SelectFunction(GPIO_OUTPUT) 65 } 66 67 // In configures a GPIO as input. 68 func (gpio *GPIO) In() { 69 gpio.SelectFunction(GPIO_INPUT) 70 } 71 72 // SelectFunction selects the function of a GPIO line. 73 func (gpio *GPIO) SelectFunction(n GPIOFunction) (err error) { 74 if n > 0b111 { 75 return fmt.Errorf("invalid GPIO function %d", n) 76 } 77 78 register := PeripheralAddress(GPFSEL0 + 4*uint32(gpio.num/10)) 79 shift := uint32((gpio.num % 10) * 3) 80 mask := uint32(0x7 << shift) 81 82 val := reg.Read(register) 83 val &= ^(mask) 84 val |= (uint32(n) << shift) & mask 85 86 reg.Write(register, val) 87 88 return 89 } 90 91 // GetFunction gets the current function of a GPIO line 92 func (gpio *GPIO) GetFunction(line int) GPIOFunction { 93 val := reg.Read(PeripheralAddress(GPFSEL0 + 4*uint32(gpio.num/10))) 94 shift := uint32((gpio.num % 10) * 3) 95 96 return GPIOFunction(val>>shift) & 0x7 97 } 98 99 // High configures a GPIO signal as high. 100 func (gpio *GPIO) High() { 101 register := PeripheralAddress(GPSET0 + 4*uint32(gpio.num/32)) 102 shift := uint32(gpio.num % 32) 103 104 reg.Write(register, 1<<shift) 105 } 106 107 // Low configures a GPIO signal as low. 108 func (gpio *GPIO) Low() { 109 register := PeripheralAddress(GPCLR0 + 4*uint32(gpio.num/32)) 110 shift := uint32(gpio.num % 32) 111 112 reg.Write(register, 1<<shift) 113 } 114 115 // Value returns the GPIO signal level. 116 func (gpio *GPIO) Value() (high bool) { 117 register := PeripheralAddress(GPLEV0 + 4*uint32(gpio.num/32)) 118 shift := uint32(gpio.num % 32) 119 120 return (reg.Read(register)>>shift)&0x1 != 0 121 } 122 123 // PullUpDown controls the pull-up or pull-down state of the line. 124 // 125 // The pull-up / pull-down state persists across power-down state 126 // of the CPU (i.e. always set the pull-up / pull-down to desired 127 // state before using a GPIO pin). 128 func (gpio *GPIO) PullUpDown(val uint32) { 129 // The control registers are shared between GPIO pins, so 130 // hold a mutex for the period. 131 gpmux.Lock() 132 defer gpmux.Unlock() 133 134 // There is a very specific documented dance (likely related 135 // to the persistence over power-down): 136 // 1 - write to control register to indicate if wanting to 137 // pull some pins up or down 138 // 2 - Wait at least 150 clock cycles for control to setup 139 // 3 - Enable the clock for the lines to be modified 140 // 4 - Wait at least 150 clock cycles to hold control signal 141 // 5 - Remove the control signal 142 // 6 - Remove the clock for the line to be modified 143 144 reg.Write(PeripheralAddress(GPPUD), uint32(val)) 145 arm.Busyloop(150) 146 147 clkRegister := PeripheralAddress(GPPUDCLK0 + 4*uint32(gpio.num/32)) 148 clkShift := uint32(gpio.num % 32) 149 150 reg.Write(clkRegister, 1<<clkShift) 151 arm.Busyloop(150) 152 153 reg.Write(PeripheralAddress(GPPUD), 0) 154 reg.Write(clkRegister, 0) 155 }