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 #include <l4/sys/irq.h> 25 #include <l4/util/util.h> 26 27 #include <math.h> // log2 28 29 #include "tcu-common.h" 30 31 32 33 // Register locations. 34 35 enum Regs : unsigned 36 { 37 Tcu_enable_status = 0x010, // TER 38 Tcu_set_enable = 0x014, // TESR 39 Tcu_clear_enable = 0x018, // TECR 40 Tcu_stop_status = 0x01c, // TSR 41 Tcu_set_stop = 0x02c, // TSSR 42 Tcu_clear_stop = 0x03c, // TSCR 43 Tcu_flag_status = 0x020, // TFR 44 Tcu_set_flag = 0x024, // TFSR 45 Tcu_clear_flag = 0x028, // TFCR 46 Tcu_mask_status = 0x030, // TMR 47 Tcu_set_mask = 0x034, // TMSR 48 Tcu_clear_mask = 0x038, // TMCR 49 50 // Channel-related locations. 51 52 Tcu_full_data_value_base = 0x040, // TDFRn 53 Tcu_half_data_value_base = 0x044, // TDHRn 54 Tcu_counter_base = 0x048, // TCNTn 55 Tcu_control_base = 0x04c, // TCRn 56 57 // Block size/step/offset for the above register set. 58 59 Tcu_data_block_offset = 0x010, 60 }; 61 62 // Field definitions. 63 64 // Enable/stop register bits. 65 66 enum Channel_bit_numbers : unsigned 67 { 68 Channel_wdt = 16, // WDTS only 69 70 // Enable/stop/flag/mask bit numbers. 71 72 Channel_ost = 15, // OSTEN/OSTS/OSTFLAG 73 Channel_tcu7 = 7, // TCEN7/STOP7/FFLAG7/SFLAG7 74 Channel_tcu6 = 6, // TCEN6/STOP6/FFLAG6/SFLAG6 75 Channel_tcu5 = 5, // TCEN5/STOP5/FFLAG5/SFLAG5 76 Channel_tcu4 = 4, // TCEN4/STOP4/FFLAG4/SFLAG4 77 Channel_tcu3 = 3, // TCEN3/STOP3/FFLAG3/SFLAG3 78 Channel_tcu2 = 2, // TCEN2/STOP2/FFLAG2/SFLAG2 79 Channel_tcu1 = 1, // TCEN1/STOP1/FFLAG1/SFLAG1 80 Channel_tcu0 = 0, // TCEN0/STOP0/FFLAG0/SFLAG0 81 }; 82 83 // Flag/mask register bits. 84 85 enum Flag_bit_numbers : unsigned 86 { 87 Half_match_wdt = 24, // HFLAGW 88 89 // Flag/mask group bit offsets. 90 91 Half_match_shift = 16, 92 Full_match_shift = 0, 93 }; 94 95 // Counter data constraints. 96 97 enum Data_masks : unsigned 98 { 99 Data_mask = 0xffff, 100 }; 101 102 enum Control_bits : unsigned 103 { 104 Count_prescale_field_mask = 0x7, // PRESCALE 105 Count_prescale_max = 5, // CLK/1024 106 Count_prescale_field_shift = 3, 107 108 Count_clock_field_mask = 0x7, 109 Count_clock_exclk = 4, // EXT_EN 110 Count_clock_rtclk = 2, // RTC_EN 111 Count_clock_pclk = 1, // PCK_EN 112 Count_clock_field_shift = 0, 113 }; 114 115 116 117 // Channel abstraction. 118 119 Tcu_channel::Tcu_channel(l4_addr_t addr, uint8_t channel, l4_cap_idx_t irq) 120 : _channel(channel), _irq(irq) 121 { 122 _regs = new Hw::Mmio_register_block<32>(addr); 123 } 124 125 // Utility methods. 126 // NOTE: Also defined in the CPM abstraction, should be consolidated. 127 128 uint32_t 129 Tcu_channel::get_field(uint32_t reg, uint32_t mask, uint8_t shift) 130 { 131 return (_regs[reg] & (mask << shift)) >> shift; 132 } 133 134 void 135 Tcu_channel::set_field(uint32_t reg, uint32_t mask, uint8_t shift, uint32_t value) 136 { 137 _regs[reg] = (_regs[reg] & (~(mask << shift))) | ((mask & value) << shift); 138 } 139 140 // Operation methods. 141 142 uint8_t 143 Tcu_channel::get_clock() 144 { 145 return (uint8_t) get_field(Tcu_control_base + _channel * Tcu_data_block_offset, 146 Count_clock_field_mask, Count_clock_field_shift); 147 } 148 149 void 150 Tcu_channel::set_clock(uint8_t clock) 151 { 152 153 set_field(Tcu_control_base + _channel * Tcu_data_block_offset, 154 Count_clock_field_mask, Count_clock_field_shift, clock); 155 } 156 157 uint32_t 158 Tcu_channel::get_prescale() 159 { 160 return 1UL << (2 * get_field(Tcu_control_base + _channel * Tcu_data_block_offset, 161 Count_prescale_field_mask, Count_prescale_field_shift)); 162 } 163 164 void 165 Tcu_channel::set_prescale(uint32_t prescale) 166 { 167 // Obtain the log4 value for prescale. 168 169 uint32_t value = (uint32_t) log2(prescale) / 2; 170 171 set_field(Tcu_control_base + _channel * Tcu_data_block_offset, 172 Count_prescale_field_mask, Count_prescale_field_shift, 173 value > Count_prescale_max ? Count_prescale_max : value); 174 } 175 176 void 177 Tcu_channel::disable() 178 { 179 _regs[Tcu_clear_enable] = 1UL << _channel; 180 } 181 182 void 183 Tcu_channel::enable() 184 { 185 _regs[Tcu_set_enable] = 1UL << _channel; 186 } 187 188 bool 189 Tcu_channel::is_enabled() 190 { 191 return _regs[Tcu_enable_status] & (1UL << _channel); 192 } 193 194 uint32_t 195 Tcu_channel::get_counter() 196 { 197 return _regs[Tcu_counter_base + _channel * Tcu_data_block_offset] & Data_mask; 198 } 199 200 void 201 Tcu_channel::set_counter(uint32_t value) 202 { 203 _regs[Tcu_counter_base + _channel * Tcu_data_block_offset] = value & Data_mask; 204 } 205 206 uint8_t 207 Tcu_channel::get_count_mode() 208 { 209 return 0; 210 } 211 212 void 213 Tcu_channel::set_count_mode(uint8_t mode) 214 { 215 if (mode != 0) 216 throw -L4_EINVAL; 217 } 218 219 uint32_t 220 Tcu_channel::get_full_data_value() 221 { 222 return _regs[Tcu_full_data_value_base + _channel * Tcu_data_block_offset] & Data_mask; 223 } 224 225 void 226 Tcu_channel::set_full_data_value(uint32_t value) 227 { 228 _regs[Tcu_full_data_value_base + _channel * Tcu_data_block_offset] = value & Data_mask; 229 } 230 231 uint32_t 232 Tcu_channel::get_half_data_value() 233 { 234 return _regs[Tcu_half_data_value_base + _channel * Tcu_data_block_offset] & Data_mask; 235 } 236 237 void 238 Tcu_channel::set_half_data_value(uint32_t value) 239 { 240 _regs[Tcu_half_data_value_base + _channel * Tcu_data_block_offset] = value & Data_mask; 241 } 242 243 bool 244 Tcu_channel::get_full_data_mask() 245 { 246 return _regs[Tcu_mask_status] & (1UL << (_channel + Full_match_shift)); 247 } 248 249 void 250 Tcu_channel::set_full_data_mask(bool masked) 251 { 252 _regs[masked ? Tcu_set_mask : Tcu_clear_mask] = (1UL << (_channel + Full_match_shift)); 253 } 254 255 bool 256 Tcu_channel::get_half_data_mask() 257 { 258 return _regs[Tcu_mask_status] & (1UL << (_channel + Half_match_shift)); 259 } 260 261 void 262 Tcu_channel::set_half_data_mask(bool masked) 263 { 264 _regs[masked ? Tcu_set_mask : Tcu_clear_mask] = (1UL << (_channel + Half_match_shift)); 265 } 266 267 // Wait indefinitely for an interrupt request, returning true if one was delivered. 268 269 bool 270 Tcu_channel::wait_for_irq() 271 { 272 bool irq = !l4_error(l4_irq_receive(_irq, L4_IPC_NEVER)) && have_interrupt(); 273 274 if (irq) 275 ack_irq(); 276 277 return irq; 278 } 279 280 // Wait up to the given timeout (in microseconds) for an interrupt request, 281 // returning true if one was delivered. 282 283 bool 284 Tcu_channel::wait_for_irq(unsigned int timeout) 285 { 286 bool irq = !l4_error(l4_irq_receive(_irq, l4_timeout(L4_IPC_TIMEOUT_NEVER, l4util_micros2l4to(timeout)))) && have_interrupt(); 287 288 if (irq) 289 ack_irq(); 290 291 return irq; 292 } 293 294 // Acknowledge an interrupt condition. 295 296 void 297 Tcu_channel::ack_irq() 298 { 299 _regs[Tcu_clear_flag] = 1UL << _channel; 300 } 301 302 // Return whether an interrupt is pending on the given channel. 303 304 bool 305 Tcu_channel::have_interrupt() 306 { 307 return _regs[Tcu_flag_status] & (1UL << _channel) ? true : false; 308 } 309 310 311 312 // Peripheral abstraction. 313 314 Tcu_chip::Tcu_chip(l4_addr_t start, l4_addr_t end) 315 : _start(start), _end(end) 316 { 317 } 318 319 // Obtain a channel object. 320 321 Tcu_channel * 322 Tcu_chip::get_channel(uint8_t channel, l4_cap_idx_t irq) 323 { 324 if (channel < num_channels()) 325 return _get_channel(_start, channel, irq); 326 else 327 throw -L4_EINVAL; 328 }