summaryrefslogblamecommitdiffstats
path: root/hw/intel-hda.c
blob: 127e81888b7a79a5d96bbd0ba85bd3b6bb5cc51b (plain) (tree)
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089




















                                                                       
                



                           
                



                                                                           




                                                       



                                            





                                                           
                                                             



                             
                                                


                                                                      
                                                               
 


                                 
                         
                  
     
                                 
                          

 


                                                                
                                                               
 

                       



             

                                                              
                  

                         

                                                       

























































































                                                                               
                      











                                
                 
































                                                                                   











                                                      
                                    




















                                                  
                                             







                                                           








                                                     





                                                                  
                             














                                                                                  

                                            





























                                                                      
                                                    






























                                                                                      

                                                   


























                                                                                   

                            
                       

                     



                                                 


                  
                 























                                                           
                                                                               




















                                                                
                                                      

















                                                                     

                                                   
                                                    
                                             












                                                                     
                                                                                                 
 
                  

                         

                                                            

                                 
                                                     


                                                       












                                                                                       




                                                                                         






















































                                                                                           
                                   














                                                                  
                                                           


                                                                 
                                                            












































                                                                           
                           
                                                     
                                          



                               

                           



























































































































































































































































































































































































































































                                                                                          













                                                   

  



                                                                           
                  
                                                               


                            
                                                  

                      

                                                            
                                                     
                                   









                                                          
                                             
 




                                                                               

                                                           
                                              


                                                






                                                           
                                          


                                                          
                        
                                    

 










































































                                                            







                                                                
                                          



                                                



                                                



                                           

 




                                           

  




                                                                       
                               
                         

 





                                              
                                              

  
                                          
 
                                              
                                          
                                                      
 

                                   

















                                                         
/*
 * Copyright (C) 2010 Red Hat, Inc.
 *
 * written by Gerd Hoffmann <kraxel@redhat.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 or
 * (at your option) version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "hw.h"
#include "pci.h"
#include "msi.h"
#include "qemu-timer.h"
#include "audiodev.h"
#include "intel-hda.h"
#include "intel-hda-defs.h"
#include "dma.h"

/* --------------------------------------------------------------------- */
/* hda bus                                                               */

static Property hda_props[] = {
    DEFINE_PROP_UINT32("cad", HDACodecDevice, cad, -1),
    DEFINE_PROP_END_OF_LIST()
};

static const TypeInfo hda_codec_bus_info = {
    .name = TYPE_HDA_BUS,
    .parent = TYPE_BUS,
    .instance_size = sizeof(HDACodecBus),
};

void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus,
                        hda_codec_response_func response,
                        hda_codec_xfer_func xfer)
{
    qbus_create_inplace(&bus->qbus, TYPE_HDA_BUS, dev, NULL);
    bus->response = response;
    bus->xfer = xfer;
}

static int hda_codec_dev_init(DeviceState *qdev)
{
    HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, qdev->parent_bus);
    HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev);
    HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev);

    if (dev->cad == -1) {
        dev->cad = bus->next_cad;
    }
    if (dev->cad >= 15) {
        return -1;
    }
    bus->next_cad = dev->cad + 1;
    return cdc->init(dev);
}

static int hda_codec_dev_exit(DeviceState *qdev)
{
    HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev);
    HDACodecDeviceClass *cdc = HDA_CODEC_DEVICE_GET_CLASS(dev);

    if (cdc->exit) {
        cdc->exit(dev);
    }
    return 0;
}

HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad)
{
    BusChild *kid;
    HDACodecDevice *cdev;

    QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
        DeviceState *qdev = kid->child;
        cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
        if (cdev->cad == cad) {
            return cdev;
        }
    }
    return NULL;
}

void hda_codec_response(HDACodecDevice *dev, bool solicited, uint32_t response)
{
    HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
    bus->response(dev, solicited, response);
}

bool hda_codec_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
                    uint8_t *buf, uint32_t len)
{
    HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
    return bus->xfer(dev, stnr, output, buf, len);
}

/* --------------------------------------------------------------------- */
/* intel hda emulation                                                   */

typedef struct IntelHDAStream IntelHDAStream;
typedef struct IntelHDAState IntelHDAState;
typedef struct IntelHDAReg IntelHDAReg;

typedef struct bpl {
    uint64_t addr;
    uint32_t len;
    uint32_t flags;
} bpl;

struct IntelHDAStream {
    /* registers */
    uint32_t ctl;
    uint32_t lpib;
    uint32_t cbl;
    uint32_t lvi;
    uint32_t fmt;
    uint32_t bdlp_lbase;
    uint32_t bdlp_ubase;

    /* state */
    bpl      *bpl;
    uint32_t bentries;
    uint32_t bsize, be, bp;
};

struct IntelHDAState {
    PCIDevice pci;
    const char *name;
    HDACodecBus codecs;

    /* registers */
    uint32_t g_ctl;
    uint32_t wake_en;
    uint32_t state_sts;
    uint32_t int_ctl;
    uint32_t int_sts;
    uint32_t wall_clk;

    uint32_t corb_lbase;
    uint32_t corb_ubase;
    uint32_t corb_rp;
    uint32_t corb_wp;
    uint32_t corb_ctl;
    uint32_t corb_sts;
    uint32_t corb_size;

    uint32_t rirb_lbase;
    uint32_t rirb_ubase;
    uint32_t rirb_wp;
    uint32_t rirb_cnt;
    uint32_t rirb_ctl;
    uint32_t rirb_sts;
    uint32_t rirb_size;

    uint32_t dp_lbase;
    uint32_t dp_ubase;

    uint32_t icw;
    uint32_t irr;
    uint32_t ics;

    /* streams */
    IntelHDAStream st[8];

    /* state */
    MemoryRegion mmio;
    uint32_t rirb_count;
    int64_t wall_base_ns;

    /* debug logging */
    const IntelHDAReg *last_reg;
    uint32_t last_val;
    uint32_t last_write;
    uint32_t last_sec;
    uint32_t repeat_count;

    /* properties */
    uint32_t debug;
    uint32_t msi;
};

struct IntelHDAReg {
    const char *name;      /* register name */
    uint32_t   size;       /* size in bytes */
    uint32_t   reset;      /* reset value */
    uint32_t   wmask;      /* write mask */
    uint32_t   wclear;     /* write 1 to clear bits */
    uint32_t   offset;     /* location in IntelHDAState */
    uint32_t   shift;      /* byte access entries for dwords */
    uint32_t   stream;
    void       (*whandler)(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old);
    void       (*rhandler)(IntelHDAState *d, const IntelHDAReg *reg);
};

static void intel_hda_reset(DeviceState *dev);

/* --------------------------------------------------------------------- */

static target_phys_addr_t intel_hda_addr(uint32_t lbase, uint32_t ubase)
{
    target_phys_addr_t addr;

#if TARGET_PHYS_ADDR_BITS == 32
    addr = lbase;
#else
    addr = ubase;
    addr <<= 32;
    addr |= lbase;
#endif
    return addr;
}

static void intel_hda_update_int_sts(IntelHDAState *d)
{
    uint32_t sts = 0;
    uint32_t i;

    /* update controller status */
    if (d->rirb_sts & ICH6_RBSTS_IRQ) {
        sts |= (1 << 30);
    }
    if (d->rirb_sts & ICH6_RBSTS_OVERRUN) {
        sts |= (1 << 30);
    }
    if (d->state_sts & d->wake_en) {
        sts |= (1 << 30);
    }

    /* update stream status */
    for (i = 0; i < 8; i++) {
        /* buffer completion interrupt */
        if (d->st[i].ctl & (1 << 26)) {
            sts |= (1 << i);
        }
    }

    /* update global status */
    if (sts & d->int_ctl) {
        sts |= (1 << 31);
    }

    d->int_sts = sts;
}

static void intel_hda_update_irq(IntelHDAState *d)
{
    int msi = d->msi && msi_enabled(&d->pci);
    int level;

    intel_hda_update_int_sts(d);
    if (d->int_sts & (1 << 31) && d->int_ctl & (1 << 31)) {
        level = 1;
    } else {
        level = 0;
    }
    dprint(d, 2, "%s: level %d [%s]\n", __FUNCTION__,
           level, msi ? "msi" : "intx");
    if (msi) {
        if (level) {
            msi_notify(&d->pci, 0);
        }
    } else {
        qemu_set_irq(d->pci.irq[0], level);
    }
}

static int intel_hda_send_command(IntelHDAState *d, uint32_t verb)
{
    uint32_t cad, nid, data;
    HDACodecDevice *codec;
    HDACodecDeviceClass *cdc;

    cad = (verb >> 28) & 0x0f;
    if (verb & (1 << 27)) {
        /* indirect node addressing, not specified in HDA 1.0 */
        dprint(d, 1, "%s: indirect node addressing (guest bug?)\n", __FUNCTION__);
        return -1;
    }
    nid = (verb >> 20) & 0x7f;
    data = verb & 0xfffff;

    codec = hda_codec_find(&d->codecs, cad);
    if (codec == NULL) {
        dprint(d, 1, "%s: addressed non-existing codec\n", __FUNCTION__);
        return -1;
    }
    cdc = HDA_CODEC_DEVICE_GET_CLASS(codec);
    cdc->command(codec, nid, data);
    return 0;
}

static void intel_hda_corb_run(IntelHDAState *d)
{
    target_phys_addr_t addr;
    uint32_t rp, verb;

    if (d->ics & ICH6_IRS_BUSY) {
        dprint(d, 2, "%s: [icw] verb 0x%08x\n", __FUNCTION__, d->icw);
        intel_hda_send_command(d, d->icw);
        return;
    }

    for (;;) {
        if (!(d->corb_ctl & ICH6_CORBCTL_RUN)) {
            dprint(d, 2, "%s: !run\n", __FUNCTION__);
            return;
        }
        if ((d->corb_rp & 0xff) == d->corb_wp) {
            dprint(d, 2, "%s: corb ring empty\n", __FUNCTION__);
            return;
        }
        if (d->rirb_count == d->rirb_cnt) {
            dprint(d, 2, "%s: rirb count reached\n", __FUNCTION__);
            return;
        }

        rp = (d->corb_rp + 1) & 0xff;
        addr = intel_hda_addr(d->corb_lbase, d->corb_ubase);
        verb = ldl_le_pci_dma(&d->pci, addr + 4*rp);
        d->corb_rp = rp;

        dprint(d, 2, "%s: [rp 0x%x] verb 0x%08x\n", __FUNCTION__, rp, verb);
        intel_hda_send_command(d, verb);
    }
}

static void intel_hda_response(HDACodecDevice *dev, bool solicited, uint32_t response)
{
    HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
    IntelHDAState *d = container_of(bus, IntelHDAState, codecs);
    target_phys_addr_t addr;
    uint32_t wp, ex;

    if (d->ics & ICH6_IRS_BUSY) {
        dprint(d, 2, "%s: [irr] response 0x%x, cad 0x%x\n",
               __FUNCTION__, response, dev->cad);
        d->irr = response;
        d->ics &= ~(ICH6_IRS_BUSY | 0xf0);
        d->ics |= (ICH6_IRS_VALID | (dev->cad << 4));
        return;
    }

    if (!(d->rirb_ctl & ICH6_RBCTL_DMA_EN)) {
        dprint(d, 1, "%s: rirb dma disabled, drop codec response\n", __FUNCTION__);
        return;
    }

    ex = (solicited ? 0 : (1 << 4)) | dev->cad;
    wp = (d->rirb_wp + 1) & 0xff;
    addr = intel_hda_addr(d->rirb_lbase, d->rirb_ubase);
    stl_le_pci_dma(&d->pci, addr + 8*wp, response);
    stl_le_pci_dma(&d->pci, addr + 8*wp + 4, ex);
    d->rirb_wp = wp;

    dprint(d, 2, "%s: [wp 0x%x] response 0x%x, extra 0x%x\n",
           __FUNCTION__, wp, response, ex);

    d->rirb_count++;
    if (d->rirb_count == d->rirb_cnt) {
        dprint(d, 2, "%s: rirb count reached (%d)\n", __FUNCTION__, d->rirb_count);
        if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) {
            d->rirb_sts |= ICH6_RBSTS_IRQ;
            intel_hda_update_irq(d);
        }
    } else if ((d->corb_rp & 0xff) == d->corb_wp) {
        dprint(d, 2, "%s: corb ring empty (%d/%d)\n", __FUNCTION__,
               d->rirb_count, d->rirb_cnt);
        if (d->rirb_ctl & ICH6_RBCTL_IRQ_EN) {
            d->rirb_sts |= ICH6_RBSTS_IRQ;
            intel_hda_update_irq(d);
        }
    }
}

static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output,
                           uint8_t *buf, uint32_t len)
{
    HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, dev->qdev.parent_bus);
    IntelHDAState *d = container_of(bus, IntelHDAState, codecs);
    target_phys_addr_t addr;
    uint32_t s, copy, left;
    IntelHDAStream *st;
    bool irq = false;

    st = output ? d->st + 4 : d->st;
    for (s = 0; s < 4; s++) {
        if (stnr == ((st[s].ctl >> 20) & 0x0f)) {
            st = st + s;
            break;
        }
    }
    if (s == 4) {
        return false;
    }
    if (st->bpl == NULL) {
        return false;
    }
    if (st->ctl & (1 << 26)) {
        /*
         * Wait with the next DMA xfer until the guest
         * has acked the buffer completion interrupt
         */
        return false;
    }

    left = len;
    while (left > 0) {
        copy = left;
        if (copy > st->bsize - st->lpib)
            copy = st->bsize - st->lpib;
        if (copy > st->bpl[st->be].len - st->bp)
            copy = st->bpl[st->be].len - st->bp;

        dprint(d, 3, "dma: entry %d, pos %d/%d, copy %d\n",
               st->be, st->bp, st->bpl[st->be].len, copy);

        pci_dma_rw(&d->pci, st->bpl[st->be].addr + st->bp, buf, copy, !output);
        st->lpib += copy;
        st->bp += copy;
        buf += copy;
        left -= copy;

        if (st->bpl[st->be].len == st->bp) {
            /* bpl entry filled */
            if (st->bpl[st->be].flags & 0x01) {
                irq = true;
            }
            st->bp = 0;
            st->be++;
            if (st->be == st->bentries) {
                /* bpl wrap around */
                st->be = 0;
                st->lpib = 0;
            }
        }
    }
    if (d->dp_lbase & 0x01) {
        addr = intel_hda_addr(d->dp_lbase & ~0x01, d->dp_ubase);
        stl_le_pci_dma(&d->pci, addr + 8*s, st->lpib);
    }
    dprint(d, 3, "dma: --\n");

    if (irq) {
        st->ctl |= (1 << 26); /* buffer completion interrupt */
        intel_hda_update_irq(d);
    }
    return true;
}

static void intel_hda_parse_bdl(IntelHDAState *d, IntelHDAStream *st)
{
    target_phys_addr_t addr;
    uint8_t buf[16];
    uint32_t i;

    addr = intel_hda_addr(st->bdlp_lbase, st->bdlp_ubase);
    st->bentries = st->lvi +1;
    g_free(st->bpl);
    st->bpl = g_malloc(sizeof(bpl) * st->bentries);
    for (i = 0; i < st->bentries; i++, addr += 16) {
        pci_dma_read(&d->pci, addr, buf, 16);
        st->bpl[i].addr  = le64_to_cpu(*(uint64_t *)buf);
        st->bpl[i].len   = le32_to_cpu(*(uint32_t *)(buf + 8));
        st->bpl[i].flags = le32_to_cpu(*(uint32_t *)(buf + 12));
        dprint(d, 1, "bdl/%d: 0x%" PRIx64 " +0x%x, 0x%x\n",
               i, st->bpl[i].addr, st->bpl[i].len, st->bpl[i].flags);
    }

    st->bsize = st->cbl;
    st->lpib  = 0;
    st->be    = 0;
    st->bp    = 0;
}

static void intel_hda_notify_codecs(IntelHDAState *d, uint32_t stream, bool running, bool output)
{
    BusChild *kid;
    HDACodecDevice *cdev;

    QTAILQ_FOREACH(kid, &d->codecs.qbus.children, sibling) {
        DeviceState *qdev = kid->child;
        HDACodecDeviceClass *cdc;

        cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
        cdc = HDA_CODEC_DEVICE_GET_CLASS(cdev);
        if (cdc->stream) {
            cdc->stream(cdev, stream, running, output);
        }
    }
}

/* --------------------------------------------------------------------- */

static void intel_hda_set_g_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    if ((d->g_ctl & ICH6_GCTL_RESET) == 0) {
        intel_hda_reset(&d->pci.qdev);
    }
}

static void intel_hda_set_wake_en(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    intel_hda_update_irq(d);
}

static void intel_hda_set_state_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    intel_hda_update_irq(d);
}

static void intel_hda_set_int_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    intel_hda_update_irq(d);
}

static void intel_hda_get_wall_clk(IntelHDAState *d, const IntelHDAReg *reg)
{
    int64_t ns;

    ns = qemu_get_clock_ns(vm_clock) - d->wall_base_ns;
    d->wall_clk = (uint32_t)(ns * 24 / 1000);  /* 24 MHz */
}

static void intel_hda_set_corb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    intel_hda_corb_run(d);
}

static void intel_hda_set_corb_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    intel_hda_corb_run(d);
}

static void intel_hda_set_rirb_wp(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    if (d->rirb_wp & ICH6_RIRBWP_RST) {
        d->rirb_wp = 0;
    }
}

static void intel_hda_set_rirb_sts(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    intel_hda_update_irq(d);

    if ((old & ICH6_RBSTS_IRQ) && !(d->rirb_sts & ICH6_RBSTS_IRQ)) {
        /* cleared ICH6_RBSTS_IRQ */
        d->rirb_count = 0;
        intel_hda_corb_run(d);
    }
}

static void intel_hda_set_ics(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    if (d->ics & ICH6_IRS_BUSY) {
        intel_hda_corb_run(d);
    }
}

static void intel_hda_set_st_ctl(IntelHDAState *d, const IntelHDAReg *reg, uint32_t old)
{
    bool output = reg->stream >= 4;
    IntelHDAStream *st = d->st + reg->stream;

    if (st->ctl & 0x01) {
        /* reset */
        dprint(d, 1, "st #%d: reset\n", reg->stream);
        st->ctl = 0;
    }
    if ((st->ctl & 0x02) != (old & 0x02)) {
        uint32_t stnr = (st->ctl >> 20) & 0x0f;
        /* run bit flipped */
        if (st->ctl & 0x02) {
            /* start */
            dprint(d, 1, "st #%d: start %d (ring buf %d bytes)\n",
                   reg->stream, stnr, st->cbl);
            intel_hda_parse_bdl(d, st);
            intel_hda_notify_codecs(d, stnr, true, output);
        } else {
            /* stop */
            dprint(d, 1, "st #%d: stop %d\n", reg->stream, stnr);
            intel_hda_notify_codecs(d, stnr, false, output);
        }
    }
    intel_hda_update_irq(d);
}

/* --------------------------------------------------------------------- */

#define ST_REG(_n, _o) (0x80 + (_n) * 0x20 + (_o))

static const struct IntelHDAReg regtab[] = {
    /* global */
    [ ICH6_REG_GCAP ] = {
        .name     = "GCAP",
        .size     = 2,
        .reset    = 0x4401,
    },
    [ ICH6_REG_VMIN ] = {
        .name     = "VMIN",
        .size     = 1,
    },
    [ ICH6_REG_VMAJ ] = {
        .name     = "VMAJ",
        .size     = 1,
        .reset    = 1,
    },
    [ ICH6_REG_OUTPAY ] = {
        .name     = "OUTPAY",
        .size     = 2,
        .reset    = 0x3c,
    },
    [ ICH6_REG_INPAY ] = {
        .name     = "INPAY",
        .size     = 2,
        .reset    = 0x1d,
    },
    [ ICH6_REG_GCTL ] = {
        .name     = "GCTL",
        .size     = 4,
        .wmask    = 0x0103,
        .offset   = offsetof(IntelHDAState, g_ctl),
        .whandler = intel_hda_set_g_ctl,
    },
    [ ICH6_REG_WAKEEN ] = {
        .name     = "WAKEEN",
        .size     = 2,
        .wmask    = 0x7fff,
        .offset   = offsetof(IntelHDAState, wake_en),
        .whandler = intel_hda_set_wake_en,
    },
    [ ICH6_REG_STATESTS ] = {
        .name     = "STATESTS",
        .size     = 2,
        .wmask    = 0x7fff,
        .wclear   = 0x7fff,
        .offset   = offsetof(IntelHDAState, state_sts),
        .whandler = intel_hda_set_state_sts,
    },

    /* interrupts */
    [ ICH6_REG_INTCTL ] = {
        .name     = "INTCTL",
        .size     = 4,
        .wmask    = 0xc00000ff,
        .offset   = offsetof(IntelHDAState, int_ctl),
        .whandler = intel_hda_set_int_ctl,
    },
    [ ICH6_REG_INTSTS ] = {
        .name     = "INTSTS",
        .size     = 4,
        .wmask    = 0xc00000ff,
        .wclear   = 0xc00000ff,
        .offset   = offsetof(IntelHDAState, int_sts),
    },

    /* misc */
    [ ICH6_REG_WALLCLK ] = {
        .name     = "WALLCLK",
        .size     = 4,
        .offset   = offsetof(IntelHDAState, wall_clk),
        .rhandler = intel_hda_get_wall_clk,
    },
    [ ICH6_REG_WALLCLK + 0x2000 ] = {
        .name     = "WALLCLK(alias)",
        .size     = 4,
        .offset   = offsetof(IntelHDAState, wall_clk),
        .rhandler = intel_hda_get_wall_clk,
    },

    /* dma engine */
    [ ICH6_REG_CORBLBASE ] = {
        .name     = "CORBLBASE",
        .size     = 4,
        .wmask    = 0xffffff80,
        .offset   = offsetof(IntelHDAState, corb_lbase),
    },
    [ ICH6_REG_CORBUBASE ] = {
        .name     = "CORBUBASE",
        .size     = 4,
        .wmask    = 0xffffffff,
        .offset   = offsetof(IntelHDAState, corb_ubase),
    },
    [ ICH6_REG_CORBWP ] = {
        .name     = "CORBWP",
        .size     = 2,
        .wmask    = 0xff,
        .offset   = offsetof(IntelHDAState, corb_wp),
        .whandler = intel_hda_set_corb_wp,
    },
    [ ICH6_REG_CORBRP ] = {
        .name     = "CORBRP",
        .size     = 2,
        .wmask    = 0x80ff,
        .offset   = offsetof(IntelHDAState, corb_rp),
    },
    [ ICH6_REG_CORBCTL ] = {
        .name     = "CORBCTL",
        .size     = 1,
        .wmask    = 0x03,
        .offset   = offsetof(IntelHDAState, corb_ctl),
        .whandler = intel_hda_set_corb_ctl,
    },
    [ ICH6_REG_CORBSTS ] = {
        .name     = "CORBSTS",
        .size     = 1,
        .wmask    = 0x01,
        .wclear   = 0x01,
        .offset   = offsetof(IntelHDAState, corb_sts),
    },
    [ ICH6_REG_CORBSIZE ] = {
        .name     = "CORBSIZE",
        .size     = 1,
        .reset    = 0x42,
        .offset   = offsetof(IntelHDAState, corb_size),
    },
    [ ICH6_REG_RIRBLBASE ] = {
        .name     = "RIRBLBASE",
        .size     = 4,
        .wmask    = 0xffffff80,
        .offset   = offsetof(IntelHDAState, rirb_lbase),
    },
    [ ICH6_REG_RIRBUBASE ] = {
        .name     = "RIRBUBASE",
        .size     = 4,
        .wmask    = 0xffffffff,
        .offset   = offsetof(IntelHDAState, rirb_ubase),
    },
    [ ICH6_REG_RIRBWP ] = {
        .name     = "RIRBWP",
        .size     = 2,
        .wmask    = 0x8000,
        .offset   = offsetof(IntelHDAState, rirb_wp),
        .whandler = intel_hda_set_rirb_wp,
    },
    [ ICH6_REG_RINTCNT ] = {
        .name     = "RINTCNT",
        .size     = 2,
        .wmask    = 0xff,
        .offset   = offsetof(IntelHDAState, rirb_cnt),
    },
    [ ICH6_REG_RIRBCTL ] = {
        .name     = "RIRBCTL",
        .size     = 1,
        .wmask    = 0x07,
        .offset   = offsetof(IntelHDAState, rirb_ctl),
    },
    [ ICH6_REG_RIRBSTS ] = {
        .name     = "RIRBSTS",
        .size     = 1,
        .wmask    = 0x05,
        .wclear   = 0x05,
        .offset   = offsetof(IntelHDAState, rirb_sts),
        .whandler = intel_hda_set_rirb_sts,
    },
    [ ICH6_REG_RIRBSIZE ] = {
        .name     = "RIRBSIZE",
        .size     = 1,
        .reset    = 0x42,
        .offset   = offsetof(IntelHDAState, rirb_size),
    },

    [ ICH6_REG_DPLBASE ] = {
        .name     = "DPLBASE",
        .size     = 4,
        .wmask    = 0xffffff81,
        .offset   = offsetof(IntelHDAState, dp_lbase),
    },
    [ ICH6_REG_DPUBASE ] = {
        .name     = "DPUBASE",
        .size     = 4,
        .wmask    = 0xffffffff,
        .offset   = offsetof(IntelHDAState, dp_ubase),
    },

    [ ICH6_REG_IC ] = {
        .name     = "ICW",
        .size     = 4,
        .wmask    = 0xffffffff,
        .offset   = offsetof(IntelHDAState, icw),
    },
    [ ICH6_REG_IR ] = {
        .name     = "IRR",
        .size     = 4,
        .offset   = offsetof(IntelHDAState, irr),
    },
    [ ICH6_REG_IRS ] = {
        .name     = "ICS",
        .size     = 2,
        .wmask    = 0x0003,
        .wclear   = 0x0002,
        .offset   = offsetof(IntelHDAState, ics),
        .whandler = intel_hda_set_ics,
    },

#define HDA_STREAM(_t, _i)                                            \
    [ ST_REG(_i, ICH6_REG_SD_CTL) ] = {                               \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " CTL",                          \
        .size     = 4,                                                \
        .wmask    = 0x1cff001f,                                       \
        .offset   = offsetof(IntelHDAState, st[_i].ctl),              \
        .whandler = intel_hda_set_st_ctl,                             \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_CTL) + 2] = {                            \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " CTL(stnr)",                    \
        .size     = 1,                                                \
        .shift    = 16,                                               \
        .wmask    = 0x00ff0000,                                       \
        .offset   = offsetof(IntelHDAState, st[_i].ctl),              \
        .whandler = intel_hda_set_st_ctl,                             \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_STS)] = {                                \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " CTL(sts)",                     \
        .size     = 1,                                                \
        .shift    = 24,                                               \
        .wmask    = 0x1c000000,                                       \
        .wclear   = 0x1c000000,                                       \
        .offset   = offsetof(IntelHDAState, st[_i].ctl),              \
        .whandler = intel_hda_set_st_ctl,                             \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_LPIB) ] = {                              \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " LPIB",                         \
        .size     = 4,                                                \
        .offset   = offsetof(IntelHDAState, st[_i].lpib),             \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_LPIB) + 0x2000 ] = {                     \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " LPIB(alias)",                  \
        .size     = 4,                                                \
        .offset   = offsetof(IntelHDAState, st[_i].lpib),             \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_CBL) ] = {                               \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " CBL",                          \
        .size     = 4,                                                \
        .wmask    = 0xffffffff,                                       \
        .offset   = offsetof(IntelHDAState, st[_i].cbl),              \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_LVI) ] = {                               \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " LVI",                          \
        .size     = 2,                                                \
        .wmask    = 0x00ff,                                           \
        .offset   = offsetof(IntelHDAState, st[_i].lvi),              \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_FIFOSIZE) ] = {                          \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " FIFOS",                        \
        .size     = 2,                                                \
        .reset    = HDA_BUFFER_SIZE,                                  \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_FORMAT) ] = {                            \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " FMT",                          \
        .size     = 2,                                                \
        .wmask    = 0x7f7f,                                           \
        .offset   = offsetof(IntelHDAState, st[_i].fmt),              \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_BDLPL) ] = {                             \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " BDLPL",                        \
        .size     = 4,                                                \
        .wmask    = 0xffffff80,                                       \
        .offset   = offsetof(IntelHDAState, st[_i].bdlp_lbase),       \
    },                                                                \
    [ ST_REG(_i, ICH6_REG_SD_BDLPU) ] = {                             \
        .stream   = _i,                                               \
        .name     = _t stringify(_i) " BDLPU",                        \
        .size     = 4,                                                \
        .wmask    = 0xffffffff,                                       \
        .offset   = offsetof(IntelHDAState, st[_i].bdlp_ubase),       \
    },                                                                \

    HDA_STREAM("IN", 0)
    HDA_STREAM("IN", 1)
    HDA_STREAM("IN", 2)
    HDA_STREAM("IN", 3)

    HDA_STREAM("OUT", 4)
    HDA_STREAM("OUT", 5)
    HDA_STREAM("OUT", 6)
    HDA_STREAM("OUT", 7)

};

static const IntelHDAReg *intel_hda_reg_find(IntelHDAState *d, target_phys_addr_t addr)
{
    const IntelHDAReg *reg;

    if (addr >= sizeof(regtab)/sizeof(regtab[0])) {
        goto noreg;
    }
    reg = regtab+addr;
    if (reg->name == NULL) {
        goto noreg;
    }
    return reg;

noreg:
    dprint(d, 1, "unknown register, addr 0x%x\n", (int) addr);
    return NULL;
}

static uint32_t *intel_hda_reg_addr(IntelHDAState *d, const IntelHDAReg *reg)
{
    uint8_t *addr = (void*)d;

    addr += reg->offset;
    return (uint32_t*)addr;
}

static void intel_hda_reg_write(IntelHDAState *d, const IntelHDAReg *reg, uint32_t val,
                                uint32_t wmask)
{
    uint32_t *addr;
    uint32_t old;

    if (!reg) {
        return;
    }

    if (d->debug) {
        time_t now = time(NULL);
        if (d->last_write && d->last_reg == reg && d->last_val == val) {
            d->repeat_count++;
            if (d->last_sec != now) {
                dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
                d->last_sec = now;
                d->repeat_count = 0;
            }
        } else {
            if (d->repeat_count) {
                dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
            }
            dprint(d, 2, "write %-16s: 0x%x (%x)\n", reg->name, val, wmask);
            d->last_write = 1;
            d->last_reg   = reg;
            d->last_val   = val;
            d->last_sec   = now;
            d->repeat_count = 0;
        }
    }
    assert(reg->offset != 0);

    addr = intel_hda_reg_addr(d, reg);
    old = *addr;

    if (reg->shift) {
        val <<= reg->shift;
        wmask <<= reg->shift;
    }
    wmask &= reg->wmask;
    *addr &= ~wmask;
    *addr |= wmask & val;
    *addr &= ~(val & reg->wclear);

    if (reg->whandler) {
        reg->whandler(d, reg, old);
    }
}

static uint32_t intel_hda_reg_read(IntelHDAState *d, const IntelHDAReg *reg,
                                   uint32_t rmask)
{
    uint32_t *addr, ret;

    if (!reg) {
        return 0;
    }

    if (reg->rhandler) {
        reg->rhandler(d, reg);
    }

    if (reg->offset == 0) {
        /* constant read-only register */
        ret = reg->reset;
    } else {
        addr = intel_hda_reg_addr(d, reg);
        ret = *addr;
        if (reg->shift) {
            ret >>= reg->shift;
        }
        ret &= rmask;
    }
    if (d->debug) {
        time_t now = time(NULL);
        if (!d->last_write && d->last_reg == reg && d->last_val == ret) {
            d->repeat_count++;
            if (d->last_sec != now) {
                dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
                d->last_sec = now;
                d->repeat_count = 0;
            }
        } else {
            if (d->repeat_count) {
                dprint(d, 2, "previous register op repeated %d times\n", d->repeat_count);
            }
            dprint(d, 2, "read  %-16s: 0x%x (%x)\n", reg->name, ret, rmask);
            d->last_write = 0;
            d->last_reg   = reg;
            d->last_val   = ret;
            d->last_sec   = now;
            d->repeat_count = 0;
        }
    }
    return ret;
}

static void intel_hda_regs_reset(IntelHDAState *d)
{
    uint32_t *addr;
    int i;

    for (i = 0; i < sizeof(regtab)/sizeof(regtab[0]); i++) {
        if (regtab[i].name == NULL) {
            continue;
        }
        if (regtab[i].offset == 0) {
            continue;
        }
        addr = intel_hda_reg_addr(d, regtab + i);
        *addr = regtab[i].reset;
    }
}

/* --------------------------------------------------------------------- */

static void intel_hda_mmio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
{
    IntelHDAState *d = opaque;
    const IntelHDAReg *reg = intel_hda_reg_find(d, addr);

    intel_hda_reg_write(d, reg, val, 0xff);
}

static void intel_hda_mmio_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
{
    IntelHDAState *d = opaque;
    const IntelHDAReg *reg = intel_hda_reg_find(d, addr);

    intel_hda_reg_write(d, reg, val, 0xffff);
}

static void intel_hda_mmio_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
{
    IntelHDAState *d = opaque;
    const IntelHDAReg *reg = intel_hda_reg_find(d, addr);

    intel_hda_reg_write(d, reg, val, 0xffffffff);
}

static uint32_t intel_hda_mmio_readb(void *opaque, target_phys_addr_t addr)
{
    IntelHDAState *d = opaque;
    const IntelHDAReg *reg = intel_hda_reg_find(d, addr);

    return intel_hda_reg_read(d, reg, 0xff);
}

static uint32_t intel_hda_mmio_readw(void *opaque, target_phys_addr_t addr)
{
    IntelHDAState *d = opaque;
    const IntelHDAReg *reg = intel_hda_reg_find(d, addr);

    return intel_hda_reg_read(d, reg, 0xffff);
}

static uint32_t intel_hda_mmio_readl(void *opaque, target_phys_addr_t addr)
{
    IntelHDAState *d = opaque;
    const IntelHDAReg *reg = intel_hda_reg_find(d, addr);

    return intel_hda_reg_read(d, reg, 0xffffffff);
}

static const MemoryRegionOps intel_hda_mmio_ops = {
    .old_mmio = {
        .read = {
            intel_hda_mmio_readb,
            intel_hda_mmio_readw,
            intel_hda_mmio_readl,
        },
        .write = {
            intel_hda_mmio_writeb,
            intel_hda_mmio_writew,
            intel_hda_mmio_writel,
        },
    },
    .endianness = DEVICE_NATIVE_ENDIAN,
};

/* --------------------------------------------------------------------- */

static void intel_hda_reset(DeviceState *dev)
{
    BusChild *kid;
    IntelHDAState *d = DO_UPCAST(IntelHDAState, pci.qdev, dev);
    HDACodecDevice *cdev;

    intel_hda_regs_reset(d);
    d->wall_base_ns = qemu_get_clock_ns(vm_clock);

    /* reset codecs */
    QTAILQ_FOREACH(kid, &d->codecs.qbus.children, sibling) {
        DeviceState *qdev = kid->child;
        cdev = DO_UPCAST(HDACodecDevice, qdev, qdev);
        device_reset(DEVICE(cdev));
        d->state_sts |= (1 << cdev->cad);
    }
    intel_hda_update_irq(d);
}

static int intel_hda_init(PCIDevice *pci)
{
    IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci);
    uint8_t *conf = d->pci.config;

    d->name = object_get_typename(OBJECT(d));

    pci_config_set_interrupt_pin(conf, 1);

    /* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */
    conf[0x40] = 0x01;

    memory_region_init_io(&d->mmio, &intel_hda_mmio_ops, d,
                          "intel-hda", 0x4000);
    pci_register_bar(&d->pci, 0, 0, &d->mmio);
    if (d->msi) {
        msi_init(&d->pci, 0x50, 1, true, false);
    }

    hda_codec_bus_init(&d->pci.qdev, &d->codecs,
                       intel_hda_response, intel_hda_xfer);

    return 0;
}

static void intel_hda_exit(PCIDevice *pci)
{
    IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci);

    msi_uninit(&d->pci);
    memory_region_destroy(&d->mmio);
}

static int intel_hda_post_load(void *opaque, int version)
{
    IntelHDAState* d = opaque;
    int i;

    dprint(d, 1, "%s\n", __FUNCTION__);
    for (i = 0; i < ARRAY_SIZE(d->st); i++) {
        if (d->st[i].ctl & 0x02) {
            intel_hda_parse_bdl(d, &d->st[i]);
        }
    }
    intel_hda_update_irq(d);
    return 0;
}

static const VMStateDescription vmstate_intel_hda_stream = {
    .name = "intel-hda-stream",
    .version_id = 1,
    .fields = (VMStateField []) {
        VMSTATE_UINT32(ctl, IntelHDAStream),
        VMSTATE_UINT32(lpib, IntelHDAStream),
        VMSTATE_UINT32(cbl, IntelHDAStream),
        VMSTATE_UINT32(lvi, IntelHDAStream),
        VMSTATE_UINT32(fmt, IntelHDAStream),
        VMSTATE_UINT32(bdlp_lbase, IntelHDAStream),
        VMSTATE_UINT32(bdlp_ubase, IntelHDAStream),
        VMSTATE_END_OF_LIST()
    }
};

static const VMStateDescription vmstate_intel_hda = {
    .name = "intel-hda",
    .version_id = 1,
    .post_load = intel_hda_post_load,
    .fields = (VMStateField []) {
        VMSTATE_PCI_DEVICE(pci, IntelHDAState),

        /* registers */
        VMSTATE_UINT32(g_ctl, IntelHDAState),
        VMSTATE_UINT32(wake_en, IntelHDAState),
        VMSTATE_UINT32(state_sts, IntelHDAState),
        VMSTATE_UINT32(int_ctl, IntelHDAState),
        VMSTATE_UINT32(int_sts, IntelHDAState),
        VMSTATE_UINT32(wall_clk, IntelHDAState),
        VMSTATE_UINT32(corb_lbase, IntelHDAState),
        VMSTATE_UINT32(corb_ubase, IntelHDAState),
        VMSTATE_UINT32(corb_rp, IntelHDAState),
        VMSTATE_UINT32(corb_wp, IntelHDAState),
        VMSTATE_UINT32(corb_ctl, IntelHDAState),
        VMSTATE_UINT32(corb_sts, IntelHDAState),
        VMSTATE_UINT32(corb_size, IntelHDAState),
        VMSTATE_UINT32(rirb_lbase, IntelHDAState),
        VMSTATE_UINT32(rirb_ubase, IntelHDAState),
        VMSTATE_UINT32(rirb_wp, IntelHDAState),
        VMSTATE_UINT32(rirb_cnt, IntelHDAState),
        VMSTATE_UINT32(rirb_ctl, IntelHDAState),
        VMSTATE_UINT32(rirb_sts, IntelHDAState),
        VMSTATE_UINT32(rirb_size, IntelHDAState),
        VMSTATE_UINT32(dp_lbase, IntelHDAState),
        VMSTATE_UINT32(dp_ubase, IntelHDAState),
        VMSTATE_UINT32(icw, IntelHDAState),
        VMSTATE_UINT32(irr, IntelHDAState),
        VMSTATE_UINT32(ics, IntelHDAState),
        VMSTATE_STRUCT_ARRAY(st, IntelHDAState, 8, 0,
                             vmstate_intel_hda_stream,
                             IntelHDAStream),

        /* additional state info */
        VMSTATE_UINT32(rirb_count, IntelHDAState),
        VMSTATE_INT64(wall_base_ns, IntelHDAState),

        VMSTATE_END_OF_LIST()
    }
};

static Property intel_hda_properties[] = {
    DEFINE_PROP_UINT32("debug", IntelHDAState, debug, 0),
    DEFINE_PROP_UINT32("msi", IntelHDAState, msi, 1),
    DEFINE_PROP_END_OF_LIST(),
};

static void intel_hda_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);

    k->init = intel_hda_init;
    k->exit = intel_hda_exit;
    k->vendor_id = PCI_VENDOR_ID_INTEL;
    k->device_id = 0x2668;
    k->revision = 1;
    k->class_id = PCI_CLASS_MULTIMEDIA_HD_AUDIO;
    dc->desc = "Intel HD Audio Controller";
    dc->reset = intel_hda_reset;
    dc->vmsd = &vmstate_intel_hda;
    dc->props = intel_hda_properties;
}

static TypeInfo intel_hda_info = {
    .name          = "intel-hda",
    .parent        = TYPE_PCI_DEVICE,
    .instance_size = sizeof(IntelHDAState),
    .class_init    = intel_hda_class_init,
};

static void hda_codec_device_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *k = DEVICE_CLASS(klass);
    k->init = hda_codec_dev_init;
    k->exit = hda_codec_dev_exit;
    k->bus_type = TYPE_HDA_BUS;
    k->props = hda_props;
}

static TypeInfo hda_codec_device_type_info = {
    .name = TYPE_HDA_CODEC_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(HDACodecDevice),
    .abstract = true,
    .class_size = sizeof(HDACodecDeviceClass),
    .class_init = hda_codec_device_class_init,
};

static void intel_hda_register_types(void)
{
    type_register_static(&hda_codec_bus_info);
    type_register_static(&intel_hda_info);
    type_register_static(&hda_codec_device_type_info);
}

type_init(intel_hda_register_types)

/*
 * create intel hda controller with codec attached to it,
 * so '-soundhw hda' works.
 */
int intel_hda_and_codec_init(PCIBus *bus)
{
    PCIDevice *controller;
    BusState *hdabus;
    DeviceState *codec;

    controller = pci_create_simple(bus, -1, "intel-hda");
    hdabus = QLIST_FIRST(&controller->qdev.child_bus);
    codec = qdev_create(hdabus, "hda-duplex");
    qdev_init_nofail(codec);
    return 0;
}