1 /* 2 * Timer/counter unit support. 3 * 4 * Copyright (C) 2024 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <l4/devices/hw_mmio_register_block.h> 23 #include <l4/sys/err.h> 24 25 #include <math.h> 26 27 #include "tcu-common.h" 28 29 30 31 // Register locations. 32 33 enum Regs : unsigned 34 { 35 Tcu_enable_status = 0x010, // TER 36 Tcu_set_enable = 0x014, // TESR 37 Tcu_clear_enable = 0x018, // TECR 38 Tcu_stop_status = 0x01c, // TSR 39 Tcu_set_stop = 0x02c, // TSSR 40 Tcu_clear_stop = 0x03c, // TSCR 41 Tcu_flag_status = 0x020, // TFR 42 Tcu_set_flag = 0x024, // TFSR 43 Tcu_clear_flag = 0x028, // TFCR 44 Tcu_mask_status = 0x030, // TMR 45 Tcu_set_mask = 0x034, // TMSR 46 Tcu_clear_mask = 0x038, // TMCR 47 48 // Channel-related locations. 49 50 Tcu_full_data_value_base = 0x040, // TDFRn 51 Tcu_half_data_value_base = 0x044, // TDHRn 52 Tcu_counter_base = 0x048, // TCNTn 53 Tcu_control_base = 0x04c, // TCRn 54 55 // Block size/step/offset for the above register set. 56 57 Tcu_data_block_offset = 0x010, 58 }; 59 60 // Field definitions. 61 62 // Enable/stop register bits. 63 64 enum Channel_bit_numbers : unsigned 65 { 66 Channel_wdt = 16, // WDTS only 67 68 // Enable/stop/flag/mask bit numbers. 69 70 Channel_ost = 15, // OSTEN/OSTS/OSTFLAG 71 Channel_tcu7 = 7, // TCEN7/STOP7/FFLAG7/SFLAG7 72 Channel_tcu6 = 6, // TCEN6/STOP6/FFLAG6/SFLAG6 73 Channel_tcu5 = 5, // TCEN5/STOP5/FFLAG5/SFLAG5 74 Channel_tcu4 = 4, // TCEN4/STOP4/FFLAG4/SFLAG4 75 Channel_tcu3 = 3, // TCEN3/STOP3/FFLAG3/SFLAG3 76 Channel_tcu2 = 2, // TCEN2/STOP2/FFLAG2/SFLAG2 77 Channel_tcu1 = 1, // TCEN1/STOP1/FFLAG1/SFLAG1 78 Channel_tcu0 = 0, // TCEN0/STOP0/FFLAG0/SFLAG0 79 }; 80 81 // Counter data constraints. 82 83 enum Data_masks : unsigned 84 { 85 Data_mask = 0xffff, 86 }; 87 88 enum Control_bits : unsigned 89 { 90 Count_prescale_field_mask = 0x7, // PRESCALE 91 Count_prescale_max = 5, // CLK/1024 92 Count_prescale_field_shift = 3, 93 94 Count_clock_field_mask = 0x7, 95 Count_clock_exclk = 4, // EXT_EN 96 Count_clock_rtclk = 2, // RTC_EN 97 Count_clock_pclk = 1, // PCK_EN 98 Count_clock_field_shift = 0, 99 }; 100 101 102 103 // Channel abstraction. 104 105 Tcu_channel::Tcu_channel(l4_addr_t addr, uint8_t channel) 106 : _channel(channel) 107 { 108 _regs = new Hw::Mmio_register_block<32>(addr); 109 } 110 111 // Utility methods. 112 // NOTE: Also defined in the CPM abstraction, should be consolidated. 113 114 uint32_t 115 Tcu_channel::get_field(uint32_t reg, uint32_t mask, uint8_t shift) 116 { 117 return (_regs[reg] & (mask << shift)) >> shift; 118 } 119 120 void 121 Tcu_channel::set_field(uint32_t reg, uint32_t mask, uint8_t shift, uint32_t value) 122 { 123 _regs[reg] = (_regs[reg] & (~(mask << shift))) | ((mask & value) << shift); 124 } 125 126 // Operation methods. 127 128 uint8_t 129 Tcu_channel::get_clock() 130 { 131 return (uint8_t) get_field(Tcu_control_base + _channel * Tcu_data_block_offset, 132 Count_clock_field_mask, Count_clock_field_shift); 133 } 134 135 void 136 Tcu_channel::set_clock(uint8_t clock) 137 { 138 139 set_field(Tcu_control_base + _channel * Tcu_data_block_offset, 140 Count_clock_field_mask, Count_clock_field_shift, clock); 141 } 142 143 uint32_t 144 Tcu_channel::get_prescale() 145 { 146 return 1UL << (2 * get_field(Tcu_control_base + _channel * Tcu_data_block_offset, 147 Count_prescale_field_mask, Count_prescale_field_shift)); 148 } 149 150 void 151 Tcu_channel::set_prescale(uint32_t prescale) 152 { 153 // Obtain the log4 value for prescale. 154 155 uint32_t value = (uint32_t) log2(prescale) / 2; 156 157 set_field(Tcu_control_base + _channel * Tcu_data_block_offset, 158 Count_prescale_field_mask, Count_prescale_field_shift, 159 value > Count_prescale_max ? Count_prescale_max : value); 160 } 161 162 void 163 Tcu_channel::disable() 164 { 165 _regs[Tcu_clear_enable] = 1UL << _channel; 166 } 167 168 void 169 Tcu_channel::enable() 170 { 171 _regs[Tcu_set_enable] = 1UL << _channel; 172 } 173 174 bool 175 Tcu_channel::is_enabled() 176 { 177 return _regs[Tcu_enable_status] & (1UL << _channel); 178 } 179 180 uint32_t 181 Tcu_channel::get_counter() 182 { 183 return _regs[Tcu_counter_base + _channel * Tcu_data_block_offset] & Data_mask; 184 } 185 186 void 187 Tcu_channel::set_counter(uint32_t value) 188 { 189 _regs[Tcu_counter_base + _channel * Tcu_data_block_offset] = value & Data_mask; 190 } 191 192 uint8_t 193 Tcu_channel::get_count_mode() 194 { 195 return 0; 196 } 197 198 void 199 Tcu_channel::set_count_mode(uint8_t mode) 200 { 201 if (mode != 0) 202 throw -L4_EINVAL; 203 } 204 205 uint32_t 206 Tcu_channel::get_full_data_value() 207 { 208 return _regs[Tcu_full_data_value_base + _channel * Tcu_data_block_offset] & Data_mask; 209 } 210 211 void 212 Tcu_channel::set_full_data_value(uint32_t value) 213 { 214 _regs[Tcu_full_data_value_base + _channel * Tcu_data_block_offset] = value & Data_mask; 215 } 216 217 uint32_t 218 Tcu_channel::get_half_data_value() 219 { 220 return _regs[Tcu_half_data_value_base + _channel * Tcu_data_block_offset] & Data_mask; 221 } 222 223 void 224 Tcu_channel::set_half_data_value(uint32_t value) 225 { 226 _regs[Tcu_half_data_value_base + _channel * Tcu_data_block_offset] = value & Data_mask; 227 } 228 229 230 231 // Peripheral abstraction. 232 233 Tcu_chip::Tcu_chip(l4_addr_t start, l4_addr_t end) 234 : _start(start), _end(end) 235 { 236 } 237 238 // Obtain a channel object. 239 240 Tcu_channel * 241 Tcu_chip::get_channel(uint8_t channel) 242 { 243 if (channel < num_channels()) 244 return _get_channel(_start, channel); 245 else 246 throw -L4_EINVAL; 247 }