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