1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using System.Linq; 10 using Antmicro.Renode.Peripherals.Bus; 11 using Antmicro.Renode.Peripherals.Helpers; 12 using Antmicro.Renode.Core; 13 using Antmicro.Renode.Core.Structure.Registers; 14 using Antmicro.Renode.Logging; 15 16 namespace Antmicro.Renode.Peripherals.UART 17 { 18 [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] 19 public class Cadence_UART : UARTBase, IUARTWithBufferState, IDoubleWordPeripheral, IKnownSize 20 { Cadence_UART(IMachine machine, bool clearInterruptStatusOnRead = false, ulong clockFrequency = 50000000)21 public Cadence_UART(IMachine machine, bool clearInterruptStatusOnRead = false, ulong clockFrequency = 50000000) : base(machine) 22 { 23 this.clearInterruptStatusOnRead = clearInterruptStatusOnRead; 24 this.clockFrequency = clockFrequency; 25 registers = new DoubleWordRegisterCollection(this, BuildRegisterMap()); 26 27 rxFifoOverflow = new CadenceInterruptFlag(() => false); 28 rxFifoFull = new CadenceInterruptFlag(() => Count >= FifoCapacity); 29 rxFifoTrigger = new CadenceInterruptFlag(() => Count >= (int)rxTriggerLevel.Value && rxTriggerLevel.Value > 0); 30 rxFifoEmpty = new CadenceInterruptFlag(() => Count == 0); 31 rxTimeoutError = new CadenceInterruptFlag(() => false); 32 txFifoEmpty = new CadenceInterruptFlag(() => true); 33 } 34 WriteDoubleWord(long offset, uint value)35 public void WriteDoubleWord(long offset, uint value) 36 { 37 registers.Write(offset, value); 38 } 39 ReadDoubleWord(long offset)40 public uint ReadDoubleWord(long offset) 41 { 42 return registers.Read(offset); 43 } 44 WriteChar(byte value)45 public override void WriteChar(byte value) 46 { 47 if(!RxEnabled) 48 { 49 this.Log(LogLevel.Warning, "Receiver isn't enabled, incoming byte not queued."); 50 return; 51 } 52 53 if(Count < FifoCapacity) 54 { 55 base.WriteChar(value); 56 UpdateBufferState(); 57 // Trigger the timeout interrupt immediately after each reception 58 rxTimeoutError.SetSticky(true); 59 } 60 else 61 { 62 rxFifoOverflow.SetSticky(true); 63 this.Log(LogLevel.Warning, "Rx FIFO overflowed, incoming byte not queued."); 64 } 65 UpdateSticky(); 66 UpdateInterrupts(); 67 } 68 Reset()69 public override void Reset() 70 { 71 base.Reset(); 72 registers.Reset(); 73 foreach(var flag in GetInterruptFlags()) 74 { 75 flag.Reset(); 76 } 77 UpdateInterrupts(); 78 } 79 80 public long Size => 0x80; 81 public BufferState BufferState { get; private set; } 82 83 [DefaultInterrupt] 84 public GPIO IRQ { get; } = new GPIO(); 85 86 public GPIO RxFifoFullIRQ { get; } = new GPIO(); 87 public GPIO RxFifoFillLevelTriggerIRQ { get; } = new GPIO(); 88 public GPIO RxFifoEmptyIRQ { get; } = new GPIO(); 89 90 public GPIO TxFifoEmptyIRQ { get; } = new GPIO(); 91 public GPIO TxFifoFullIRQ { get; } = new GPIO(); 92 public GPIO TxFifoFillLevelTriggerIRQ { get; } = new GPIO(); 93 public GPIO TxFifoNearlyFullIRQ { get; } = new GPIO(); 94 95 public event Action<BufferState> BufferStateChanged; 96 97 public override Bits StopBits => ConvertInternalStop(stopBitsField.Value); 98 99 public override Parity ParityBit => ConvertInternalParity(parityField.Value); 100 101 public override uint BaudRate => (uint)(clockFrequency / (clockSource.Value ? 8U : 1U) / baudGenerator.Value / (baudDivider.Value + 1)); 102 CharWritten()103 protected override void CharWritten() 104 { 105 // Intentionally leaved empty 106 } 107 QueueEmptied()108 protected override void QueueEmptied() 109 { 110 UpdateSticky(); 111 UpdateInterrupts(); 112 } 113 ConvertInternalStop(InternalStop stop)114 private static Bits ConvertInternalStop(InternalStop stop) 115 { 116 switch(stop) 117 { 118 default: 119 case InternalStop.One: 120 return Bits.One; 121 case InternalStop.OneAndHalf: 122 return Bits.OneAndAHalf; 123 case InternalStop.Two: 124 return Bits.Two; 125 } 126 } 127 ConvertInternalParity(InternalParity parity)128 private static Parity ConvertInternalParity(InternalParity parity) 129 { 130 switch(parity) 131 { 132 case InternalParity.Even: 133 return Parity.Even; 134 case InternalParity.Odd: 135 return Parity.Odd; 136 case InternalParity.Forced0: 137 return Parity.Forced0; 138 case InternalParity.Forced1: 139 return Parity.Forced1; 140 default: 141 return Parity.None; 142 } 143 } 144 UpdateSticky()145 private void UpdateSticky() 146 { 147 foreach(CadenceInterruptFlag flag in GetInterruptFlags()) 148 { 149 flag.UpdateStickyStatus(); 150 } 151 } 152 UpdateInterrupts()153 private void UpdateInterrupts() 154 { 155 IRQ.Set(GetInterruptFlags().Any(x => x.InterruptStatus)); 156 RxFifoFullIRQ.Set(rxFifoFull.InterruptStatus); 157 RxFifoFillLevelTriggerIRQ.Set(rxFifoTrigger.InterruptStatus); 158 RxFifoEmptyIRQ.Set(rxFifoEmpty.InterruptStatus); 159 TxFifoEmptyIRQ.Set(txFifoEmpty.InterruptStatus); 160 } 161 UpdateBufferState()162 private void UpdateBufferState() 163 { 164 if((!rxFifoFull.Status && BufferState == BufferState.Full) || 165 (!rxFifoEmpty.Status && BufferState == BufferState.Empty) || 166 ((rxFifoFull.Status || rxFifoEmpty.Status) && BufferState == BufferState.Ready)) 167 { 168 if(rxFifoEmpty.Status) 169 { 170 BufferState = BufferState.Empty; 171 } 172 else if(rxFifoFull.Status) 173 { 174 BufferState = BufferState.Full; 175 } 176 else 177 { 178 BufferState = BufferState.Ready; 179 } 180 181 BufferStateChanged?.Invoke(BufferState); 182 } 183 } 184 BuildRegisterMap()185 private Dictionary<long, DoubleWordRegister> BuildRegisterMap() 186 { 187 var interruptStatusFieldMode = FieldMode.Read | (clearInterruptStatusOnRead ? 0 : FieldMode.Write); 188 return new Dictionary<long, DoubleWordRegister> 189 { 190 {(long)Registers.Control, new DoubleWordRegister(this, 0x00000128) 191 .WithReservedBits(9, 23) 192 .WithTaggedFlag("stopTxBreak", 8) 193 .WithTaggedFlag("startTxBreak", 7) 194 .WithFlag(6, FieldMode.Read | FieldMode.WriteOneToClear, name: "restartRxTimeout", 195 writeCallback: 196 // Trigger the timeout interrupt immediately after each timeout counter restart 197 (_, val) => rxTimeoutError.SetSticky(val) 198 ) 199 .WithFlag(5, out txDisabledReg, name: "txDisabled") 200 .WithFlag(4, out txEnabledReg, name: "txEnabled") 201 .WithFlag(3, out rxDisabledReg, name: "rxDisabled") 202 .WithFlag(2, out rxEnabledReg, name: "rxEnabled") 203 .WithFlag(1, valueProviderCallback: _ => false, name: "txReset") 204 .WithFlag(0, valueProviderCallback: _ => false, name: "rxReset", 205 writeCallback: 206 (_, val) => { if(val) this.ClearBuffer(); } 207 ) 208 .WithWriteCallback((_, __) => 209 { 210 UpdateSticky(); 211 UpdateInterrupts(); 212 }) 213 }, 214 {(long)Registers.Mode, new DoubleWordRegister(this) 215 .WithReservedBits(14, 18) 216 .WithTag("accessSize", 12, 2) 217 .WithReservedBits(10, 2) 218 .WithTag("channelMode", 8, 2) 219 .WithEnumField(6, 2, out stopBitsField, name: "stopBits") 220 .WithEnumField(3, 3, out parityField, name: "parityType") 221 .WithTag("characterLength", 1, 2) 222 .WithFlag(0, out clockSource, name: "clockSourceSelect") 223 }, 224 {(long)Registers.InterruptEnable, new DoubleWordRegister(this) 225 .WithReservedBits(14, 18) 226 .WithTaggedFlag("rxBreakDetectInterruptEnable", 13) 227 .WithTaggedFlag("txFifoOverflowInterruptEnable", 12) 228 .WithTaggedFlag("txFifoNearlyFullInterruptEnable", 11) 229 .WithTaggedFlag("txFifoTriggerInterruptEnable", 10) 230 .WithTaggedFlag("deltaModemStatusInterruptEnable", 9) 231 .WithFlag(8, FieldMode.Write, 232 writeCallback: (_, val) => rxTimeoutError.InterruptEnable(val), 233 name: "rxTimeoutErrorInterruptEnable" 234 ) 235 .WithTaggedFlag("rxParityErrorInterruptEnable", 7) 236 .WithTaggedFlag("rxFramingErrorInterruptEnable", 6) 237 .WithFlag(5, FieldMode.Write, 238 writeCallback: (_, val) => rxFifoOverflow.InterruptEnable(val), 239 name: "rxFifoOverflowInterruptEnable" 240 ) 241 .WithTaggedFlag("txFifoFullInterruptEnable", 4) 242 .WithFlag(3, FieldMode.Write, 243 writeCallback: (_, val) => txFifoEmpty.InterruptEnable(val), 244 name: "txFifoEmptyInterruptEnable" 245 ) 246 .WithFlag(2, FieldMode.Write, 247 writeCallback: (_, val) => rxFifoFull.InterruptEnable(val), 248 name: "rxFifoFullInterruptEnable" 249 ) 250 .WithFlag(1, FieldMode.Write, 251 writeCallback: (_, val) => rxFifoEmpty.InterruptEnable(val), 252 name: "rxFifoEmptyInterruptEnable" 253 ) 254 .WithFlag(0, FieldMode.Write, 255 writeCallback: (_, val) => rxFifoTrigger.InterruptEnable(val), 256 name: "rxFifoTriggerInterruptEnable" 257 ) 258 .WithWriteCallback((_, __) => UpdateInterrupts()) 259 }, 260 {(long)Registers.InterruptDisable, new DoubleWordRegister(this) 261 .WithReservedBits(14, 18) 262 .WithTaggedFlag("rxBreakDetectInterruptDisable", 13) 263 .WithTaggedFlag("txFifoOverflowInterruptDisable", 12) 264 .WithTaggedFlag("txFifoNearlyFullInterruptDisable", 11) 265 .WithTaggedFlag("txFifoTriggerInterruptDisable", 10) 266 .WithTaggedFlag("deltaModemStatusInterruptDisable", 9) 267 .WithFlag(8, FieldMode.Write, 268 writeCallback: (_, val) => rxTimeoutError.InterruptDisable(val), 269 name: "rxTimeoutErrorInterruptDisable" 270 ) 271 .WithTaggedFlag("rxParityErrorInterruptDisable", 7) 272 .WithTaggedFlag("rxFramingErrorInterruptDisable", 6) 273 .WithFlag(5, FieldMode.Write, 274 writeCallback: (_, val) => rxFifoOverflow.InterruptDisable(val), 275 name: "rxFifoOverflowInterruptDisable" 276 ) 277 .WithTaggedFlag("txFifoFullInterruptDisable", 4) 278 .WithFlag(3, FieldMode.Write, 279 writeCallback: (_, val) => txFifoEmpty.InterruptDisable(val), 280 name: "txFifoEmptyInterruptDisable" 281 ) 282 .WithFlag(2, FieldMode.Write, 283 writeCallback: (_, val) => rxFifoFull.InterruptDisable(val), 284 name: "rxFifoFullInterruptDisable" 285 ) 286 .WithFlag(1, FieldMode.Write, 287 writeCallback: (_, val) => rxFifoEmpty.InterruptDisable(val), 288 name: "rxFifoEmptyInterruptDisable" 289 ) 290 .WithFlag(0, FieldMode.Write, 291 writeCallback: (_, val) => rxFifoTrigger.InterruptDisable(val), 292 name: "rxFifoTriggerInterruptDisable" 293 ) 294 .WithWriteCallback((_, __) => UpdateInterrupts()) 295 }, 296 {(long)Registers.InterruptMask, new DoubleWordRegister(this) 297 .WithReservedBits(14, 18) 298 .WithTaggedFlag("rxBreakDetectInterruptMask", 13) 299 .WithTaggedFlag("txFifoOverflowInterruptMask", 12) 300 .WithTaggedFlag("txFifoNearlyFullInterruptMask", 11) 301 .WithTaggedFlag("txFifoTriggerInterruptMask", 10) 302 .WithTaggedFlag("deltaModemStatusInterruptMask", 9) 303 .WithFlag(8, FieldMode.Read, 304 valueProviderCallback: (_) => rxTimeoutError.InterruptMask, 305 name: "rxTimeoutErrorInterruptMask" 306 ) 307 .WithTaggedFlag("rxParityErrorInterruptMask", 7) 308 .WithTaggedFlag("rxFramingErrorInterruptMask", 6) 309 .WithFlag(5, FieldMode.Read, 310 valueProviderCallback: (_) => rxFifoOverflow.InterruptMask, 311 name: "rxFifoOverflowInterruptMask" 312 ) 313 .WithTaggedFlag("txFifoFullInterruptMask", 4) 314 .WithFlag(3, FieldMode.Read, 315 valueProviderCallback: (_) => txFifoEmpty.InterruptMask, 316 name: "txFifoEmptyInterruptMask" 317 ) 318 .WithFlag(2, FieldMode.Read, 319 valueProviderCallback: (_) => rxFifoFull.InterruptMask, 320 name: "rxFifoFullInterruptMask" 321 ) 322 .WithFlag(1, FieldMode.Read, 323 valueProviderCallback: (_) => rxFifoEmpty.InterruptMask, 324 name: "rxFifoEmptyInterruptMask" 325 ) 326 .WithFlag(0, FieldMode.Read, 327 valueProviderCallback: (_) => rxFifoTrigger.InterruptMask, 328 name: "rxFifoTriggerInterruptMask" 329 ) 330 }, 331 {(long)Registers.ChannelInterruptStatus, new DoubleWordRegister(this) 332 .WithReservedBits(14, 18) 333 .WithTaggedFlag("rxBreakDetectInterruptStatus", 13) 334 .WithTaggedFlag("txFifoOverflowInterruptStatus", 12) 335 .WithTaggedFlag("txFifoNearlyFullInterruptStatus", 11) 336 .WithTaggedFlag("txFifoTriggerInterruptStatus", 10) 337 .WithTaggedFlag("deltaModemStatusInterruptStatus", 9) 338 .WithFlag(8, interruptStatusFieldMode, 339 valueProviderCallback: (_) => rxTimeoutError.StickyStatus, 340 readCallback: (_, __) => rxTimeoutError.ClearSticky(clearInterruptStatusOnRead), 341 writeCallback: (_, val) => rxTimeoutError.ClearSticky(val && !clearInterruptStatusOnRead), 342 name: "rxTimeoutErrorInterruptStatus" 343 ) 344 .WithTaggedFlag("rxParityErrorInterruptStatus", 7) 345 .WithTaggedFlag("rxFramingErrorInterruptStatus", 6) 346 .WithFlag(5, interruptStatusFieldMode, 347 valueProviderCallback: (_) => rxFifoOverflow.StickyStatus, 348 readCallback: (_, __) => rxFifoOverflow.ClearSticky(clearInterruptStatusOnRead), 349 writeCallback: (_, val) => rxFifoOverflow.ClearSticky(val && !clearInterruptStatusOnRead), 350 name: "rxFifoOverflowInterruptStatus" 351 ) 352 .WithTaggedFlag("txFifoFullInterruptStatus", 4) 353 .WithFlag(3, interruptStatusFieldMode, 354 valueProviderCallback: (_) => txFifoEmpty.StickyStatus, 355 // There is no sense to clear the txFifoEmptyInterruptStatus flag, because a Tx FIFO is always empty 356 name: "txFifoEmptyInterruptStatus" 357 ) 358 .WithFlag(2, interruptStatusFieldMode, 359 valueProviderCallback: (_) => rxFifoFull.StickyStatus, 360 readCallback: (_, __) => rxFifoFull.ClearSticky(clearInterruptStatusOnRead), 361 writeCallback: (_, val) => rxFifoFull.ClearSticky(val && !clearInterruptStatusOnRead), 362 name: "rxFifoFullInterruptStatus" 363 ) 364 .WithFlag(1, interruptStatusFieldMode, 365 valueProviderCallback: (_) => rxFifoEmpty.StickyStatus, 366 readCallback: (_, __) => rxFifoEmpty.ClearSticky(clearInterruptStatusOnRead), 367 writeCallback: (_, val) => rxFifoEmpty.ClearSticky(val && !clearInterruptStatusOnRead), 368 name: "rxFifoEmptyInterruptMStatus" 369 ) 370 .WithFlag(0, interruptStatusFieldMode, 371 valueProviderCallback: (_) => rxFifoTrigger.StickyStatus, 372 readCallback: (_, __) => rxFifoTrigger.ClearSticky(clearInterruptStatusOnRead), 373 writeCallback: (_, val) => rxFifoTrigger.ClearSticky(val && !clearInterruptStatusOnRead), 374 name: "rxFifoTriggerInterruptStatus" 375 ) 376 .WithReadCallback((_, __) => 377 { 378 if(clearInterruptStatusOnRead) 379 { 380 UpdateSticky(); 381 UpdateInterrupts(); 382 } 383 }) 384 .WithWriteCallback((_, __) => 385 { 386 if(!clearInterruptStatusOnRead) 387 { 388 UpdateSticky(); 389 UpdateInterrupts(); 390 } 391 }) 392 }, 393 {(long)Registers.BaudRateGenerator, new DoubleWordRegister(this, resetValue: 0x0000028B) 394 .WithReservedBits(16, 16) 395 .WithValueField(0, 16, out baudGenerator, writeCallback: (oldVal, newVal) => 396 { 397 if(newVal == 0) 398 { 399 // https://docs.xilinx.com/r/en-US/ug585-zynq-7000-SoC-TRM/Baud-Rate-Generator 400 this.Log(LogLevel.Warning, "Attempt to write 0 to Baud Rate Generator register was ignored. It can be programmed with a value between 1 and 65535."); 401 baudGenerator.Value = oldVal; 402 } 403 }, name: "baudRateGenerator") 404 }, 405 {(long)Registers.RxFifoTriggerLevel, new DoubleWordRegister(this, 0x00000020) 406 .WithReservedBits(6, 26) 407 .WithValueField(0, 6, out rxTriggerLevel) 408 .WithWriteCallback((_, __) => 409 { 410 UpdateSticky(); 411 UpdateInterrupts(); 412 }) 413 }, 414 {(long)Registers.ChannelStatus, new DoubleWordRegister(this) 415 .WithReservedBits(15, 17) 416 .WithFlag(14, FieldMode.Read, valueProviderCallback: _ => false, name: "txFifoTriggerStatus") 417 .WithTaggedFlag("rxFlowDelayTriggerStatus", 12) 418 .WithTaggedFlag("txStateMachineActiveStatus", 11) 419 .WithTaggedFlag("rxStateMachineActiveStatus", 10) 420 .WithReservedBits(5, 4) 421 .WithFlag(4, FieldMode.Read, valueProviderCallback: _ => false, name: "txFifoFullStatus") 422 .WithFlag(3, FieldMode.Read, 423 valueProviderCallback: _ => txFifoEmpty.Status, 424 name: "txFifoEmptyStatus") 425 .WithFlag(2, FieldMode.Read, 426 valueProviderCallback: _ => rxFifoFull.Status, 427 name: "rxFifoFullStatus") 428 .WithFlag(1, FieldMode.Read, 429 valueProviderCallback: _ => rxFifoEmpty.Status, 430 name: "rxFifoEmptyStatus") 431 .WithFlag(0, FieldMode.Read, 432 valueProviderCallback: _ => rxFifoTrigger.Status, 433 name: "rxFifoTriggerStatus") 434 }, 435 {(long)Registers.RxTxFifo, new DoubleWordRegister(this) 436 .WithValueField(0, 8, 437 writeCallback: (_, value) => 438 { 439 if(!TxEnabled) 440 { 441 this.Log(LogLevel.Warning, "Trying to write to a disabled Tx."); 442 return; 443 } 444 this.TransmitCharacter((byte)value); 445 }, 446 valueProviderCallback: _ => 447 { 448 if(!RxEnabled) 449 { 450 this.Log(LogLevel.Warning, "Reading from disabled Rx FIFO."); 451 } 452 if(!TryGetCharacter(out var character)) 453 { 454 this.Log(LogLevel.Warning, "Reading from an empty Rx FIFO, dummy data returned."); 455 } 456 return character; 457 }) 458 .WithWriteCallback((_, __) => 459 { 460 UpdateSticky(); 461 UpdateInterrupts(); 462 }) 463 .WithReadCallback((_, __) => 464 { 465 UpdateBufferState(); 466 UpdateSticky(); 467 UpdateInterrupts(); 468 }) 469 }, 470 {(long)Registers.BaudRateDivider, new DoubleWordRegister(this, resetValue: 0x0000000F) 471 .WithReservedBits(8, 24) 472 .WithValueField(0, 8, out baudDivider, name: "baudRateDivider") 473 } 474 }; 475 } 476 GetInterruptFlags()477 private IEnumerable<CadenceInterruptFlag> GetInterruptFlags() 478 { 479 yield return rxFifoOverflow; 480 yield return rxFifoFull; 481 yield return rxFifoTrigger; 482 yield return rxFifoEmpty; 483 yield return rxTimeoutError; 484 yield return txFifoEmpty; 485 } 486 487 private bool TxEnabled => txEnabledReg.Value && !txDisabledReg.Value; 488 private bool RxEnabled => rxEnabledReg.Value && !rxDisabledReg.Value; 489 490 private IFlagRegisterField txDisabledReg; 491 private IFlagRegisterField txEnabledReg; 492 private IFlagRegisterField rxDisabledReg; 493 private IFlagRegisterField rxEnabledReg; 494 private IEnumRegisterField<InternalStop> stopBitsField; 495 private IEnumRegisterField<InternalParity> parityField; 496 private IFlagRegisterField clockSource; 497 private IValueRegisterField baudGenerator; 498 private IValueRegisterField rxTriggerLevel; 499 private IValueRegisterField baudDivider; 500 501 private readonly CadenceInterruptFlag rxFifoOverflow; 502 private readonly CadenceInterruptFlag rxFifoFull; 503 private readonly CadenceInterruptFlag rxFifoTrigger; 504 private readonly CadenceInterruptFlag rxFifoEmpty; 505 private readonly CadenceInterruptFlag rxTimeoutError; 506 private readonly CadenceInterruptFlag txFifoEmpty; 507 508 private readonly DoubleWordRegisterCollection registers; 509 private readonly bool clearInterruptStatusOnRead; 510 private readonly ulong clockFrequency; 511 512 private const int FifoCapacity = 64; 513 514 private enum InternalStop 515 { 516 One = 0b00, 517 OneAndHalf = 0b01, 518 Two = 0b10 519 } 520 521 private enum InternalParity 522 { 523 Even = 0b000, 524 Odd = 0b001, 525 Forced0 = 0b010, 526 Forced1 = 0b011, 527 None = 0b100 528 } 529 530 private enum Registers : long 531 { 532 Control = 0x00, 533 Mode = 0x04, 534 InterruptEnable = 0x08, 535 InterruptDisable = 0x0c, 536 InterruptMask = 0x10, 537 ChannelInterruptStatus = 0x14, 538 BaudRateGenerator = 0x18, 539 RxTimeout = 0x1c, 540 RxFifoTriggerLevel = 0x20, 541 ModemControl = 0x24, 542 ModemStatus = 0x28, 543 ChannelStatus = 0x2c, 544 RxTxFifo = 0x30, 545 BaudRateDivider = 0x34, 546 FlowDelay = 0x38, 547 TxFifoTriggerLevel = 0x44, 548 RxFifoByteStatus = 0x48 549 } 550 } 551 } 552