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