19
#include "trace.h"
20
#include "hw/core/irq.h"
21
22
+/*
23
+ * Disable event command values. sent along with a DISEC CCC to disable certain
24
+ * events on targets.
25
+ */
26
+#define DISEC_HJ 0x08
27
+#define DISEC_CR 0x02
28
+#define DISEC_INT 0x01
29
+
30
REG32(DEVICE_CTRL, 0x00)
31
FIELD(DEVICE_CTRL, I3C_BROADCAST_ADDR_INC, 0, 1)
32
FIELD(DEVICE_CTRL, I2C_SLAVE_PRESENT, 7, 1)
364
!ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_RESUME);
365
}
366
367
+static inline uint8_t dw_i3c_ibi_slice_size(DWI3C *s)
368
+{
369
+ uint8_t ibi_slice_size = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
370
+ IBI_DATA_THLD);
371
+ /* The minimum supported slice size is 4 bytes. */
372
+ if (ibi_slice_size == 0) {
373
+ ibi_slice_size = 1;
374
+ }
375
+ ibi_slice_size *= sizeof(uint32_t);
376
+ /* maximum supported size is 63 bytes. */
377
+ if (ibi_slice_size >= 64) {
378
+ ibi_slice_size = 63;
379
+ }
380
+
381
+ return ibi_slice_size;
382
+}
383
+
384
static inline uint8_t dw_i3c_fifo_threshold_from_reg(uint8_t regval)
385
{
386
return regval = regval ? (2 << regval) : 1;
534
DEV_DYNAMIC_ADDR);
535
}
536
537
+static int dw_i3c_addr_table_index_from_addr(DWI3C *s, uint8_t addr)
538
+{
539
+ uint8_t table_size = ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
540
+ DEPTH);
541
+ for (uint8_t i = 0; i < table_size; i++) {
542
+ if (dw_i3c_target_addr(s, i) == addr) {
543
+ return i;
544
+ }
545
+ }
546
+ return -1;
547
+}
548
+
549
+static void dw_i3c_send_disec(DWI3C *s)
550
+{
551
+ uint8_t ccc = I3C_CCC_DISEC;
552
+ if (s->ibi_data.send_direct_disec) {
553
+ ccc = I3C_CCCD_DISEC;
554
+ }
555
+
556
+ dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
557
+ /*is_i2c=*/false);
558
+ dw_i3c_send_byte(s, ccc, /*is_i2c=*/false);
559
+ if (s->ibi_data.send_direct_disec) {
560
+ dw_i3c_send_start(s, s->ibi_data.disec_addr,
561
+ /*is_recv=*/false, /*is_i2c=*/false);
562
+ }
563
+ dw_i3c_send_byte(s, s->ibi_data.disec_byte, /*is_i2c=*/false);
564
+}
565
+
566
+static int dw_i3c_handle_hj(DWI3C *s)
567
+{
568
+ if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_HOT_JOIN)) {
569
+ s->ibi_data.notify_ibi_nack = true;
570
+ }
571
+
572
+ bool nack_and_disable = ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL,
573
+ HOT_JOIN_ACK_NACK_CTRL);
574
+ if (nack_and_disable) {
575
+ s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
576
+ IBI_QUEUE_STATUS,
577
+ IBI_STATUS, 1);
578
+ s->ibi_data.ibi_nacked = true;
579
+ s->ibi_data.disec_byte = DISEC_HJ;
580
+ return -1;
581
+ }
582
+ return 0;
583
+}
584
+
585
+static int dw_i3c_handle_ctlr_req(DWI3C *s, uint8_t addr)
586
+{
587
+ if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_MASTER_REQ)) {
588
+ s->ibi_data.notify_ibi_nack = true;
589
+ }
590
+
591
+ int table_offset = dw_i3c_addr_table_index_from_addr(s, addr);
592
+ /* Doesn't exist in the table, NACK it, don't DISEC. */
593
+ if (table_offset < 0) {
594
+ return -1;
595
+ }
596
+
597
+ /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
598
+ table_offset += (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
599
+ ADDR) / sizeof(uint32_t));
600
+ if (FIELD_EX32(s->regs[table_offset], DEVICE_ADDR_TABLE_LOC1, MR_REJECT)) {
601
+ s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
602
+ IBI_QUEUE_STATUS,
603
+ IBI_STATUS, 1);
604
+ s->ibi_data.ibi_nacked = true;
605
+ s->ibi_data.disec_addr = addr;
606
+ /* Tell the requester to disable controller role requests. */
607
+ s->ibi_data.disec_byte = DISEC_CR;
608
+ s->ibi_data.send_direct_disec = true;
609
+ return -1;
610
+ }
611
+ return 0;
612
+}
613
+
614
+static int dw_i3c_handle_targ_irq(DWI3C *s, uint8_t addr)
615
+{
616
+ if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_SLAVE_IRQ)) {
617
+ s->ibi_data.notify_ibi_nack = true;
618
+ }
619
+
620
+ int table_offset = dw_i3c_addr_table_index_from_addr(s, addr);
621
+ /* Doesn't exist in the table, NACK it, don't DISEC. */
622
+ if (table_offset < 0) {
623
+ return -1;
624
+ }
625
+
626
+ /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
627
+ table_offset += (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
628
+ ADDR) / sizeof(uint32_t));
629
+ if (FIELD_EX32(s->regs[table_offset], DEVICE_ADDR_TABLE_LOC1, SIR_REJECT)) {
630
+ s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
631
+ IBI_QUEUE_STATUS,
632
+ IBI_STATUS, 1);
633
+ s->ibi_data.ibi_nacked = true;
634
+ s->ibi_data.disec_addr = addr;
635
+ /* Tell the requester to disable interrupts. */
636
+ s->ibi_data.disec_byte = DISEC_INT;
637
+ s->ibi_data.send_direct_disec = true;
638
+ return -1;
639
+ }
640
+ return 0;
641
+}
642
+
643
+static int dw_i3c_ibi_handle(I3CBus *bus, uint8_t addr, bool is_recv)
644
+{
645
+ DWI3C *s = DW_I3C(bus->parent_obj.parent);
646
+
647
+ trace_dw_i3c_ibi_handle(s->cfg.id, addr, is_recv);
648
+ s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
649
+ IBI_QUEUE_STATUS, IBI_ID,
650
+ (addr << 1) | is_recv);
651
+ /* Is this a hot join request? */
652
+ if (addr == I3C_HJ_ADDR) {
653
+ return dw_i3c_handle_hj(s);
654
+ }
655
+ /* Is secondary controller requesting access? */
656
+ if (!is_recv) {
657
+ return dw_i3c_handle_ctlr_req(s, addr);
658
+ }
659
+ /* Is this a target IRQ? */
660
+ if (is_recv) {
661
+ return dw_i3c_handle_targ_irq(s, addr);
662
+ }
663
+
664
+ /* At this point the IBI should have been ACKed or NACKed. */
665
+ g_assert_not_reached();
666
+ return -1;
667
+}
668
+
669
+static int dw_i3c_ibi_recv(I3CBus *bus, uint8_t data)
670
+{
671
+ DWI3C *s = DW_I3C(bus->parent_obj.parent);
672
+ if (fifo8_is_full(&s->ibi_data.ibi_intermediate_queue)) {
673
+ return -1;
674
+ }
675
+
676
+ fifo8_push(&s->ibi_data.ibi_intermediate_queue, data);
677
+ trace_dw_i3c_ibi_recv(s->cfg.id, data);
678
+ return 0;
679
+}
680
+
681
+static void dw_i3c_ibi_queue_push(DWI3C *s)
682
+{
683
+ /* Stored value is in 32-bit chunks, convert it to byte chunks. */
684
+ uint8_t ibi_slice_size = dw_i3c_ibi_slice_size(s);
685
+ uint8_t num_slices = (fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) /
686
+ ibi_slice_size) +
687
+ ((fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) %
688
+ ibi_slice_size) ? 1 : 0);
689
+ uint8_t ibi_status_count = num_slices;
690
+ union {
691
+ uint8_t b[sizeof(uint32_t)];
692
+ uint32_t val32;
693
+ } ibi_data = {
694
+ .val32 = 0
695
+ };
696
+
697
+ /* The report was suppressed, do nothing. */
698
+ if (s->ibi_data.ibi_nacked && !s->ibi_data.notify_ibi_nack) {
699
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
700
+ DW_I3C_TRANSFER_STATE_IDLE);
701
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
702
+ DW_I3C_TRANSFER_STATUS_IDLE);
703
+ return;
704
+ }
705
+
706
+ /* If we don't have any slices to push, just push the status. */
707
+ if (num_slices == 0) {
708
+ s->ibi_data.ibi_queue_status =
709
+ FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
710
+ LAST_STATUS, 1);
711
+ fifo32_push(&s->ibi_queue, s->ibi_data.ibi_queue_status);
712
+ ibi_status_count = 1;
713
+ }
714
+
715
+ for (uint8_t i = 0; i < num_slices; i++) {
716
+ /* If this is the last slice, set LAST_STATUS. */
717
+ if (fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) <
718
+ ibi_slice_size) {
719
+ s->ibi_data.ibi_queue_status =
720
+ FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
721
+ IBI_DATA_LEN,
722
+ fifo8_num_used(&s->ibi_data.ibi_intermediate_queue));
723
+ s->ibi_data.ibi_queue_status =
724
+ FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
725
+ LAST_STATUS, 1);
726
+ } else {
727
+ s->ibi_data.ibi_queue_status =
728
+ FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
729
+ IBI_DATA_LEN, ibi_slice_size);
730
+ }
731
+
732
+ /* Push the IBI status header. */
733
+ fifo32_push(&s->ibi_queue, s->ibi_data.ibi_queue_status);
734
+ /* Move each IBI byte into a 32-bit word and push it into the queue. */
735
+ for (uint8_t j = 0; j < ibi_slice_size; ++j) {
736
+ if (fifo8_is_empty(&s->ibi_data.ibi_intermediate_queue)) {
737
+ break;
738
+ }
739
+
740
+ ibi_data.b[j & 3] = fifo8_pop(&s->ibi_data.ibi_intermediate_queue);
741
+ /* We have 32-bits, push it to the IBI FIFO. */
742
+ if ((j & 0x03) == 0x03) {
743
+ fifo32_push(&s->ibi_queue, ibi_data.val32);
744
+ ibi_data.val32 = 0;
745
+ }
746
+ }
747
+ /* If the data isn't 32-bit aligned, push the leftover bytes. */
748
+ if (ibi_slice_size & 0x03) {
749
+ fifo32_push(&s->ibi_queue, ibi_data.val32);
750
+ }
751
+
752
+ /* Clear out the data length for the next iteration. */
753
+ s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
754
+ IBI_QUEUE_STATUS, IBI_DATA_LEN, 0);
755
+ }
756
+
757
+ ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR,
758
+ fifo32_num_used(&s->ibi_queue));
759
+ ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_STATUS_CNT,
760
+ ibi_status_count);
761
+ /* Threshold is the register value + 1. */
762
+ uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
763
+ IBI_STATUS_THLD) + 1;
764
+ if (fifo32_num_used(&s->ibi_queue) >= threshold) {
765
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 1);
766
+ dw_i3c_update_irq(s);
767
+ }
768
+
769
+ /* State update. */
770
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
771
+ DW_I3C_TRANSFER_STATE_IDLE);
772
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
773
+ DW_I3C_TRANSFER_STATUS_IDLE);
774
+}
775
+
776
+static int dw_i3c_ibi_finish(I3CBus *bus)
777
+{
778
+ DWI3C *s = DW_I3C(bus->parent_obj.parent);
779
+ bool nack_and_disable_hj = ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL,
780
+ HOT_JOIN_ACK_NACK_CTRL);
781
+ if (nack_and_disable_hj || s->ibi_data.send_direct_disec) {
782
+ dw_i3c_send_disec(s);
783
+ }
784
+ dw_i3c_ibi_queue_push(s);
785
+
786
+ /* Clear out the intermediate values. */
787
+ s->ibi_data.ibi_queue_status = 0;
788
+ s->ibi_data.disec_addr = 0;
789
+ s->ibi_data.disec_byte = 0;
790
+ s->ibi_data.send_direct_disec = false;
791
+ s->ibi_data.notify_ibi_nack = false;
792
+ s->ibi_data.ibi_nacked = false;
793
+
794
+ return 0;
795
+}
796
+
797
static uint32_t dw_i3c_intr_status_r(DWI3C *s)
798
{
799
/* Only return the status whose corresponding EN bits are set. */
854
return val;
855
}
856
857
+static uint32_t dw_i3c_ibi_queue_r(DWI3C *s)
858
+{
859
+ if (fifo32_is_empty(&s->ibi_queue)) {
860
+ return 0;
861
+ }
862
+
863
+ uint32_t val = fifo32_pop(&s->ibi_queue);
864
+ ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR,
865
+ fifo32_num_used(&s->ibi_queue));
866
+ /* Threshold is the register value + 1. */
867
+ uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
868
+ IBI_STATUS_THLD) + 1;
869
+ if (fifo32_num_used(&s->ibi_queue) < threshold) {
870
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 0);
871
+ dw_i3c_update_irq(s);
872
+ }
873
+ return val;
874
+}
875
+
876
static uint32_t dw_i3c_resp_queue_port_r(DWI3C *s)
877
{
878
if (fifo32_is_empty(&s->resp_queue)) {
910
case R_INTR_FORCE:
911
value = 0;
912
break;
913
+ case R_IBI_QUEUE_DATA:
914
+ value = dw_i3c_ibi_queue_r(s);
915
+ break;
916
case R_INTR_STATUS:
917
value = dw_i3c_intr_status_r(s);
918
break;
1652
fifo32_create(&s->resp_queue, s->cfg.cmd_resp_queue_capacity_bytes);
1653
fifo32_create(&s->tx_queue, s->cfg.tx_rx_queue_capacity_bytes);
1654
fifo32_create(&s->rx_queue, s->cfg.tx_rx_queue_capacity_bytes);
1655
+ fifo32_create(&s->ibi_queue, s->cfg.ibi_queue_capacity_bytes);
1656
+ /* Arbitrarily large enough to not be an issue. */
1657
+ fifo8_create(&s->ibi_data.ibi_intermediate_queue,
1658
+ s->cfg.ibi_queue_capacity_bytes * 8);
1659
1660
s->bus = i3c_init_bus(DEVICE(s), name);
1661
+ I3CBusClass *bc = I3C_BUS_GET_CLASS(s->bus);
1662
+ bc->ibi_handle = dw_i3c_ibi_handle;
1663
+ bc->ibi_recv = dw_i3c_ibi_recv;
1664
+ bc->ibi_finish = dw_i3c_ibi_finish;
1665
}
1666
1667
static const Property dw_i3c_properties[] = {
1670
cfg.cmd_resp_queue_capacity_bytes, 0x10),
1671
DEFINE_PROP_UINT16("tx-rx-queue-capacity-bytes", DWI3C,
1672
cfg.tx_rx_queue_capacity_bytes, 0x40),
1673
+ DEFINE_PROP_UINT8("ibi-queue-capacity-bytes", DWI3C,
1674
+ cfg.ibi_queue_capacity_bytes, 0x10),
1675
DEFINE_PROP_UINT8("num-addressable-devices", DWI3C,
1676
cfg.num_addressable_devices, 8),
1677
DEFINE_PROP_UINT16("dev-addr-table-pointer", DWI3C,